diff --git a/build.zig b/build.zig index d6ba1e933089afe68fc0846fe58f8bea57fcf4db..085201e9548600307b9edb3d81c7a4b14050479f 100644 --- a/build.zig +++ b/build.zig @@ -618,7 +618,7 @@ fn addCompilerStep(b: *std.Build, options: AddCompilerStepOptions) *std.Build.St .root_source_file = b.path("src/main.zig"), .target = options.target, .optimize = options.optimize, - .max_rss = 7_100_000_000, + .max_rss = 7_500_000_000, .strip = options.strip, .sanitize_thread = options.sanitize_thread, .single_threaded = options.single_threaded, diff --git a/src/Compilation.zig b/src/Compilation.zig index 98d2fd552a10ad7d93261270cfc56df5fda3b261..567d0b0a027657b90db07977d5c308504753fc1d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -105,8 +105,9 @@ win32_resource_table: if (dev.env.supports(.win32_resource)) std.AutoArrayHashMa pub fn deinit(_: @This(), _: Allocator) void {} } = .{}, -link_error_flags: link.File.ErrorFlags = .{}, link_errors: std.ArrayListUnmanaged(link.File.ErrorMsg) = .{}, +link_errors_mutex: std.Thread.Mutex = .{}, +link_error_flags: link.File.ErrorFlags = .{}, lld_errors: std.ArrayListUnmanaged(LldError) = .{}, work_queues: [ @@ -3067,7 +3068,6 @@ pub fn totalErrorCount(comp: *Compilation) u32 { total += @intFromBool(comp.link_error_flags.no_entry_point_found); } total += @intFromBool(comp.link_error_flags.missing_libc); - total += comp.link_errors.items.len; // Compile log errors only count if there are no other errors. diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 9641268a7d134979e65626614050bbe3d019cb7c..fd7a87f50d08711c52468898a7df641d6ea28161 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -163,12 +163,12 @@ pub fn emitMir(emit: *Emit) Error!void { const zo = macho_file.getZigObject().?; const atom = zo.symbols.items[data.atom_index].getAtom(macho_file).?; const sym = &zo.symbols.items[data.sym_index]; - if (sym.flags.needs_zig_got and !is_obj_or_static_lib) { + if (sym.getSectionFlags().needs_zig_got and !is_obj_or_static_lib) { _ = try sym.getOrCreateZigGotEntry(data.sym_index, macho_file); } - const @"type": link.File.MachO.Relocation.Type = if (sym.flags.needs_zig_got and !is_obj_or_static_lib) + const @"type": link.File.MachO.Relocation.Type = if (sym.getSectionFlags().needs_zig_got and !is_obj_or_static_lib) .zig_got_load - else if (sym.flags.needs_got) + else if (sym.getSectionFlags().needs_got) // TODO: it is possible to emit .got_load here that can potentially be relaxed // however this requires always to use a MOVQ mnemonic .got diff --git a/src/arch/x86_64/Lower.zig b/src/arch/x86_64/Lower.zig index ed77f714889a2b2464dec511f30ed1bbcb86adb4..2a1918617651851b7ea1461a09769a122f2779a9 100644 --- a/src/arch/x86_64/Lower.zig +++ b/src/arch/x86_64/Lower.zig @@ -451,7 +451,7 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; }, .mov => { - if (is_obj_or_static_lib and macho_sym.flags.needs_zig_got) emit_mnemonic = .lea; + if (is_obj_or_static_lib and macho_sym.getSectionFlags().needs_zig_got) emit_mnemonic = .lea; break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; }, else => unreachable, diff --git a/src/codegen.zig b/src/codegen.zig index 2967f41dc22c62bf6d2c27e78bec036391f09325..ce1488f020007e0766dea70b361c830d45746b1b 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -924,7 +924,7 @@ fn genDeclRef( const name = decl.name.toSlice(ip); const lib_name = if (decl.getOwnedVariable(zcu)) |ov| ov.lib_name.toSlice(ip) else null; const sym_index = try macho_file.getGlobalSymbol(name, lib_name); - zo.symbols.items[sym_index].flags.needs_got = true; + zo.symbols.items[sym_index].setSectionFlags(.{ .needs_got = true }); return GenResult.mcv(.{ .load_symbol = sym_index }); } const sym_index = try zo.getOrCreateMetadataForDecl(macho_file, decl_index); diff --git a/src/link.zig b/src/link.zig index 9a754e4c6b97944bcc7e0d05ecd75e4e278f6ab0..f9ed515d26867a1409498b14260a429c52be0e30 100644 --- a/src/link.zig +++ b/src/link.zig @@ -439,6 +439,58 @@ pub const File = struct { } } + pub const ErrorWithNotes = struct { + base: *const File, + + /// Allocated index in base.errors array. + index: usize, + + /// Next available note slot. + note_slot: usize = 0, + + pub fn addMsg( + err: ErrorWithNotes, + comptime format: []const u8, + args: anytype, + ) error{OutOfMemory}!void { + const gpa = err.base.comp.gpa; + const err_msg = &err.base.comp.link_errors.items[err.index]; + err_msg.msg = try std.fmt.allocPrint(gpa, format, args); + } + + pub fn addNote( + err: *ErrorWithNotes, + comptime format: []const u8, + args: anytype, + ) error{OutOfMemory}!void { + const gpa = err.base.comp.gpa; + const err_msg = &err.base.comp.link_errors.items[err.index]; + assert(err.note_slot < err_msg.notes.len); + err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) }; + err.note_slot += 1; + } + }; + + pub fn addErrorWithNotes(base: *const File, note_count: usize) error{OutOfMemory}!ErrorWithNotes { + base.comp.link_errors_mutex.lock(); + defer base.comp.link_errors_mutex.unlock(); + const gpa = base.comp.gpa; + try base.comp.link_errors.ensureUnusedCapacity(gpa, 1); + return base.addErrorWithNotesAssumeCapacity(note_count); + } + + pub fn addErrorWithNotesAssumeCapacity(base: *const File, note_count: usize) error{OutOfMemory}!ErrorWithNotes { + const gpa = base.comp.gpa; + const index = base.comp.link_errors.items.len; + const err = base.comp.link_errors.addOneAssumeCapacity(); + err.* = .{ .msg = undefined, .notes = try gpa.alloc(ErrorMsg, note_count) }; + return .{ .base = base, .index = index }; + } + + pub fn hasErrors(base: *const File) bool { + return base.comp.link_errors.items.len > 0 or base.comp.link_error_flags.isSet(); + } + pub fn releaseLock(self: *File) void { if (self.lock) |*lock| { lock.release(); @@ -874,9 +926,23 @@ pub const File = struct { } }; - pub const ErrorFlags = struct { + pub const ErrorFlags = packed struct { no_entry_point_found: bool = false, missing_libc: bool = false, + + const Int = blk: { + const bits = @typeInfo(@This()).Struct.fields.len; + break :blk @Type(.{ + .Int = .{ + .signedness = .unsigned, + .bits = bits, + }, + }); + }; + + fn isSet(ef: ErrorFlags) bool { + return @as(Int, @bitCast(ef)) > 0; + } }; pub const ErrorMsg = struct { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 7b44403c8a45b5132f1447f4ba10f35cf7251c6e..d2d4fd26576891b5c94f9071f43553e28dd3bafb 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -995,12 +995,12 @@ pub fn growAllocSection(self: *Elf, shdr_index: u32, needed_size: u64) !void { if (maybe_phdr) |phdr| { const mem_capacity = self.allocatedVirtualSize(phdr.p_vaddr); if (needed_size > mem_capacity) { - var err = try self.addErrorWithNotes(2); - try err.addMsg(self, "fatal linker error: cannot expand load segment phdr({d}) in virtual memory", .{ + var err = try self.base.addErrorWithNotes(2); + try err.addMsg("fatal linker error: cannot expand load segment phdr({d}) in virtual memory", .{ self.phdr_to_shdr_table.get(shdr_index).?, }); - try err.addNote(self, "TODO: emit relocations to memory locations in self-hosted backends", .{}); - try err.addNote(self, "as a workaround, try increasing pre-allocated virtual memory of each segment", .{}); + try err.addNote("TODO: emit relocations to memory locations in self-hosted backends", .{}); + try err.addNote("as a workaround, try increasing pre-allocated virtual memory of each segment", .{}); } phdr.p_memsz = needed_size; @@ -1276,7 +1276,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod }; } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (self.base.hasErrors()) return error.FlushFailure; // Dedup shared objects { @@ -1423,7 +1423,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod try self.writeElfHeader(); } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (self.base.hasErrors()) return error.FlushFailure; } /// --verbose-link output @@ -2852,9 +2852,9 @@ fn writePhdrTable(self: *Elf) !void { } pub fn writeElfHeader(self: *Elf) !void { - const comp = self.base.comp; - if (comp.link_errors.items.len > 0) return; // We had errors, so skip flushing to render the output unusable + if (self.base.hasErrors()) return; // We had errors, so skip flushing to render the output unusable + const comp = self.base.comp; var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; var index: usize = 0; @@ -4298,9 +4298,9 @@ fn allocatePhdrTable(self: *Elf) error{OutOfMemory}!void { // (revisit getMaxNumberOfPhdrs()) // 2. shift everything in file to free more space for EHDR + PHDR table // TODO verify `getMaxNumberOfPhdrs()` is accurate and convert this into no-op - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, "fatal linker error: not enough space reserved for EHDR and PHDR table", .{}); - try err.addNote(self, "required 0x{x}, available 0x{x}", .{ needed_size, available_space }); + var err = try self.base.addErrorWithNotes(1); + try err.addMsg("fatal linker error: not enough space reserved for EHDR and PHDR table", .{}); + try err.addNote("required 0x{x}, available 0x{x}", .{ needed_size, available_space }); } phdr_table_load.p_filesz = needed_size + ehsize; @@ -5863,56 +5863,6 @@ pub fn tlsAddress(self: *Elf) i64 { return @intCast(phdr.p_vaddr); } -const ErrorWithNotes = struct { - /// Allocated index in comp.link_errors array. - index: usize, - - /// Next available note slot. - note_slot: usize = 0, - - pub fn addMsg( - err: ErrorWithNotes, - elf_file: *Elf, - comptime format: []const u8, - args: anytype, - ) error{OutOfMemory}!void { - const comp = elf_file.base.comp; - const gpa = comp.gpa; - const err_msg = &comp.link_errors.items[err.index]; - err_msg.msg = try std.fmt.allocPrint(gpa, format, args); - } - - pub fn addNote( - err: *ErrorWithNotes, - elf_file: *Elf, - comptime format: []const u8, - args: anytype, - ) error{OutOfMemory}!void { - const comp = elf_file.base.comp; - const gpa = comp.gpa; - const err_msg = &comp.link_errors.items[err.index]; - assert(err.note_slot < err_msg.notes.len); - err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) }; - err.note_slot += 1; - } -}; - -pub fn addErrorWithNotes(self: *Elf, note_count: usize) error{OutOfMemory}!ErrorWithNotes { - const comp = self.base.comp; - const gpa = comp.gpa; - try comp.link_errors.ensureUnusedCapacity(gpa, 1); - return self.addErrorWithNotesAssumeCapacity(note_count); -} - -fn addErrorWithNotesAssumeCapacity(self: *Elf, note_count: usize) error{OutOfMemory}!ErrorWithNotes { - const comp = self.base.comp; - const gpa = comp.gpa; - const index = comp.link_errors.items.len; - const err = comp.link_errors.addOneAssumeCapacity(); - err.* = .{ .msg = undefined, .notes = try gpa.alloc(link.File.ErrorMsg, note_count) }; - return .{ .index = index }; -} - pub fn getShString(self: Elf, off: u32) [:0]const u8 { assert(off < self.shstrtab.items.len); return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.shstrtab.items.ptr + off)), 0); @@ -5940,11 +5890,10 @@ pub fn insertDynString(self: *Elf, name: []const u8) error{OutOfMemory}!u32 { } fn reportUndefinedSymbols(self: *Elf, undefs: anytype) !void { - const comp = self.base.comp; - const gpa = comp.gpa; + const gpa = self.base.comp.gpa; const max_notes = 4; - try comp.link_errors.ensureUnusedCapacity(gpa, undefs.count()); + try self.base.comp.link_errors.ensureUnusedCapacity(gpa, undefs.count()); var it = undefs.iterator(); while (it.next()) |entry| { @@ -5953,18 +5902,18 @@ fn reportUndefinedSymbols(self: *Elf, undefs: anytype) !void { const natoms = @min(atoms.len, max_notes); const nnotes = natoms + @intFromBool(atoms.len > max_notes); - var err = try self.addErrorWithNotesAssumeCapacity(nnotes); - try err.addMsg(self, "undefined symbol: {s}", .{self.symbol(undef_index).name(self)}); + var err = try self.base.addErrorWithNotesAssumeCapacity(nnotes); + try err.addMsg("undefined symbol: {s}", .{self.symbol(undef_index).name(self)}); for (atoms[0..natoms]) |atom_index| { const atom_ptr = self.atom(atom_index).?; const file_ptr = self.file(atom_ptr.file_index).?; - try err.addNote(self, "referenced by {s}:{s}", .{ file_ptr.fmtPath(), atom_ptr.name(self) }); + try err.addNote("referenced by {s}:{s}", .{ file_ptr.fmtPath(), atom_ptr.name(self) }); } if (atoms.len > max_notes) { const remaining = atoms.len - max_notes; - try err.addNote(self, "referenced {d} more times", .{remaining}); + try err.addNote("referenced {d} more times", .{remaining}); } } } @@ -5978,19 +5927,19 @@ fn reportDuplicates(self: *Elf, dupes: anytype) error{ HasDuplicates, OutOfMemor const notes = entry.value_ptr.*; const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes); - var err = try self.addErrorWithNotes(nnotes + 1); - try err.addMsg(self, "duplicate symbol definition: {s}", .{sym.name(self)}); - try err.addNote(self, "defined by {}", .{sym.file(self).?.fmtPath()}); + var err = try self.base.addErrorWithNotes(nnotes + 1); + try err.addMsg("duplicate symbol definition: {s}", .{sym.name(self)}); + try err.addNote("defined by {}", .{sym.file(self).?.fmtPath()}); var inote: usize = 0; while (inote < @min(notes.items.len, max_notes)) : (inote += 1) { const file_ptr = self.file(notes.items[inote]).?; - try err.addNote(self, "defined by {}", .{file_ptr.fmtPath()}); + try err.addNote("defined by {}", .{file_ptr.fmtPath()}); } if (notes.items.len > max_notes) { const remaining = notes.items.len - max_notes; - try err.addNote(self, "defined {d} more times", .{remaining}); + try err.addNote("defined {d} more times", .{remaining}); } has_dupes = true; @@ -6005,16 +5954,16 @@ fn reportMissingLibraryError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(checked_paths.len); - try err.addMsg(self, format, args); + var err = try self.base.addErrorWithNotes(checked_paths.len); + try err.addMsg(format, args); for (checked_paths) |path| { - try err.addNote(self, "tried {s}", .{path}); + try err.addNote("tried {s}", .{path}); } } pub fn reportUnsupportedCpuArch(self: *Elf) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(0); - try err.addMsg(self, "fatal linker error: unsupported CPU architecture {s}", .{ + var err = try self.base.addErrorWithNotes(0); + try err.addMsg("fatal linker error: unsupported CPU architecture {s}", .{ @tagName(self.getTarget().cpu.arch), }); } @@ -6025,9 +5974,9 @@ pub fn reportParseError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, format, args); - try err.addNote(self, "while parsing {s}", .{path}); + var err = try self.base.addErrorWithNotes(1); + try err.addMsg(format, args); + try err.addNote("while parsing {s}", .{path}); } pub fn reportParseError2( @@ -6036,9 +5985,9 @@ pub fn reportParseError2( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, format, args); - try err.addNote(self, "while parsing {}", .{self.file(file_index).?.fmtPath()}); + var err = try self.base.addErrorWithNotes(1); + try err.addMsg(format, args); + try err.addNote("while parsing {}", .{self.file(file_index).?.fmtPath()}); } const FormatShdrCtx = struct { diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 53a8ca4b6929df9091c44075c3d5471c00cb54f4..bf2de7ccd90a1d9d7704eb73e8b37388bd09bc76 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -631,15 +631,12 @@ fn dataType(symbol: *const Symbol, elf_file: *Elf) u2 { } fn reportUnhandledRelocError(self: Atom, rel: elf.Elf64_Rela, elf_file: *Elf) RelocError!void { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "fatal linker error: unhandled relocation type {} at offset 0x{x}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("fatal linker error: unhandled relocation type {} at offset 0x{x}", .{ relocation.fmtRelocType(rel.r_type(), elf_file.getTarget().cpu.arch), rel.r_offset, }); - try err.addNote(elf_file, "in {}:{s}", .{ - self.file(elf_file).?.fmtPath(), - self.name(elf_file), - }); + try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) }); return error.RelocFailure; } @@ -649,15 +646,12 @@ fn reportTextRelocError( rel: elf.Elf64_Rela, elf_file: *Elf, ) RelocError!void { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ rel.r_offset, symbol.name(elf_file), }); - try err.addNote(elf_file, "in {}:{s}", .{ - self.file(elf_file).?.fmtPath(), - self.name(elf_file), - }); + try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) }); return error.RelocFailure; } @@ -667,16 +661,13 @@ fn reportPicError( rel: elf.Elf64_Rela, elf_file: *Elf, ) RelocError!void { - var err = try elf_file.addErrorWithNotes(2); - try err.addMsg(elf_file, "relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ + var err = try elf_file.base.addErrorWithNotes(2); + try err.addMsg("relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ rel.r_offset, symbol.name(elf_file), }); - try err.addNote(elf_file, "in {}:{s}", .{ - self.file(elf_file).?.fmtPath(), - self.name(elf_file), - }); - try err.addNote(elf_file, "recompile with -fPIC", .{}); + try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) }); + try err.addNote("recompile with -fPIC", .{}); return error.RelocFailure; } @@ -686,16 +677,13 @@ fn reportNoPicError( rel: elf.Elf64_Rela, elf_file: *Elf, ) RelocError!void { - var err = try elf_file.addErrorWithNotes(2); - try err.addMsg(elf_file, "relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ + var err = try elf_file.base.addErrorWithNotes(2); + try err.addMsg("relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ rel.r_offset, symbol.name(elf_file), }); - try err.addNote(elf_file, "in {}:{s}", .{ - self.file(elf_file).?.fmtPath(), - self.name(elf_file), - }); - try err.addNote(elf_file, "recompile with -fno-PIC", .{}); + try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) }); + try err.addNote("recompile with -fno-PIC", .{}); return error.RelocFailure; } @@ -1332,9 +1320,9 @@ const x86_64 = struct { try cwriter.writeInt(i32, @as(i32, @intCast(S_ + A - P)), .little); } else { x86_64.relaxGotPcTlsDesc(code[r_offset - 3 ..]) catch { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "could not relax {s}", .{@tagName(r_type)}); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("could not relax {s}", .{@tagName(r_type)}); + try err.addNote("in {}:{s} at offset 0x{x}", .{ atom.file(elf_file).?.fmtPath(), atom.name(elf_file), rel.r_offset, @@ -1479,12 +1467,12 @@ const x86_64 = struct { }, else => { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "TODO: rewrite {} when followed by {}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("TODO: rewrite {} when followed by {}", .{ relocation.fmtRelocType(rels[0].r_type(), .x86_64), relocation.fmtRelocType(rels[1].r_type(), .x86_64), }); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + try err.addNote("in {}:{s} at offset 0x{x}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file), rels[0].r_offset, @@ -1534,12 +1522,12 @@ const x86_64 = struct { }, else => { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "TODO: rewrite {} when followed by {}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("TODO: rewrite {} when followed by {}", .{ relocation.fmtRelocType(rels[0].r_type(), .x86_64), relocation.fmtRelocType(rels[1].r_type(), .x86_64), }); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + try err.addNote("in {}:{s} at offset 0x{x}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file), rels[0].r_offset, @@ -1630,12 +1618,12 @@ const x86_64 = struct { }, else => { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "fatal linker error: rewrite {} when followed by {}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("fatal linker error: rewrite {} when followed by {}", .{ relocation.fmtRelocType(rels[0].r_type(), .x86_64), relocation.fmtRelocType(rels[1].r_type(), .x86_64), }); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + try err.addNote("in {}:{s} at offset 0x{x}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file), rels[0].r_offset, @@ -1824,9 +1812,9 @@ const aarch64 = struct { aarch64_util.writeAdrpInst(pages, code); } else { // TODO: relax - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "TODO: relax ADR_GOT_PAGE", .{}); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("TODO: relax ADR_GOT_PAGE", .{}); + try err.addNote("in {}:{s} at offset 0x{x}", .{ atom.file(elf_file).?.fmtPath(), atom.name(elf_file), r_offset, @@ -2118,9 +2106,9 @@ const riscv = struct { if (S == atom_addr + @as(i64, @intCast(pair.r_offset))) break pair; } else { // TODO: implement searching forward - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "TODO: find HI20 paired reloc scanning forward", .{}); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("TODO: find HI20 paired reloc scanning forward", .{}); + try err.addNote("in {}:{s} at offset 0x{x}", .{ atom.file(elf_file).?.fmtPath(), atom.name(elf_file), rel.r_offset, diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index c0e6266bb79a8c70d612d82bba083bd65f109ed9..6b84c5dce905723f00492ca7226ce537d43ac361 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -704,9 +704,9 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { var end = start; while (end < data.len - sh_entsize and !isNull(data[end .. end + sh_entsize])) : (end += sh_entsize) {} if (!isNull(data[end .. end + sh_entsize])) { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "string not null terminated", .{}); - try err.addNote(elf_file, "in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) }); + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("string not null terminated", .{}); + try err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) }); return error.MalformedObject; } end += sh_entsize; @@ -719,9 +719,9 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { const sh_entsize: u32 = @intCast(shdr.sh_entsize); if (sh_entsize == 0) continue; // Malformed, don't split but don't error out if (shdr.sh_size % sh_entsize != 0) { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "size not a multiple of sh_entsize", .{}); - try err.addNote(elf_file, "in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) }); + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("size not a multiple of sh_entsize", .{}); + try err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) }); return error.MalformedObject; } @@ -779,10 +779,10 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const imsec = elf_file.inputMergeSection(imsec_index) orelse continue; if (imsec.offsets.items.len == 0) continue; const msub_index, const offset = imsec.findSubsection(@intCast(esym.st_value)) orelse { - var err = try elf_file.addErrorWithNotes(2); - try err.addMsg(elf_file, "invalid symbol value: {x}", .{esym.st_value}); - try err.addNote(elf_file, "for symbol {s}", .{sym.name(elf_file)}); - try err.addNote(elf_file, "in {}", .{self.fmtPath()}); + var err = try elf_file.base.addErrorWithNotes(2); + try err.addMsg("invalid symbol value: {x}", .{esym.st_value}); + try err.addNote("for symbol {s}", .{sym.name(elf_file)}); + try err.addNote("in {}", .{self.fmtPath()}); return error.MalformedObject; }; @@ -804,9 +804,9 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const imsec = elf_file.inputMergeSection(imsec_index) orelse continue; if (imsec.offsets.items.len == 0) continue; const msub_index, const offset = imsec.findSubsection(@intCast(@as(i64, @intCast(esym.st_value)) + rel.r_addend)) orelse { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "invalid relocation at offset 0x{x}", .{rel.r_offset}); - try err.addNote(elf_file, "in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) }); + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("invalid relocation at offset 0x{x}", .{rel.r_offset}); + try err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) }); return error.MalformedObject; }; const msub = elf_file.mergeSubsection(msub_index); diff --git a/src/link/Elf/eh_frame.zig b/src/link/Elf/eh_frame.zig index 3b4668da1d3b03b5cf5bb1b076edfc268c079a63..7f227a06e37992f211e276ad6df43d4f8d2a4008 100644 --- a/src/link/Elf/eh_frame.zig +++ b/src/link/Elf/eh_frame.zig @@ -591,12 +591,12 @@ const riscv = struct { }; fn reportInvalidReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela) !void { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "invalid relocation type {} at offset 0x{x}", .{ + var err = try elf_file.base.addErrorWithNotes(1); + try err.addMsg("invalid relocation type {} at offset 0x{x}", .{ relocation.fmtRelocType(rel.r_type(), elf_file.getTarget().cpu.arch), rel.r_offset, }); - try err.addNote(elf_file, "in {}:.eh_frame", .{elf_file.file(rec.file_index).?.fmtPath()}); + try err.addNote("in {}:.eh_frame", .{elf_file.file(rec.file_index).?.fmtPath()}); return error.RelocFailure; } diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index c70faba020610f4f6c29e31cd3e31e7f818d72f4..ab42e65596698a920fed3fcaf5cce4f29379f719 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -29,7 +29,7 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]co }; } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (elf_file.base.hasErrors()) return error.FlushFailure; // First, we flush relocatable object file generated with our backends. if (elf_file.zigObjectPtr()) |zig_object| { @@ -146,7 +146,7 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]co try elf_file.base.file.?.setEndPos(total_size); try elf_file.base.file.?.pwriteAll(buffer.items, 0); - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (elf_file.base.hasErrors()) return error.FlushFailure; } pub fn flushObject(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { @@ -177,7 +177,7 @@ pub fn flushObject(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]const }; } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (elf_file.base.hasErrors()) return error.FlushFailure; // Now, we are ready to resolve the symbols across all input files. // We will first resolve the files in the ZigObject, next in the parsed @@ -216,7 +216,7 @@ pub fn flushObject(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]const try elf_file.writeShdrTable(); try elf_file.writeElfHeader(); - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (elf_file.base.hasErrors()) return error.FlushFailure; } fn parsePositional(elf_file: *Elf, path: []const u8) Elf.ParseError!void { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 3e5b18e76f6776d47be4df4aecd8eae157e733c5..aa7a2a96aa9bac9b3659f7121b267bdd950410a1 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -25,8 +25,10 @@ sections: std.MultiArrayList(Section) = .{}, resolver: SymbolResolver = .{}, /// This table will be populated after `scanRelocs` has run. /// Key is symbol index. -undefs: std.AutoHashMapUnmanaged(SymbolResolver.Index, std.ArrayListUnmanaged(Ref)) = .{}, -dupes: std.AutoHashMapUnmanaged(SymbolResolver.Index, std.ArrayListUnmanaged(File.Index)) = .{}, +undefs: std.AutoArrayHashMapUnmanaged(SymbolResolver.Index, std.ArrayListUnmanaged(Ref)) = .{}, +undefs_mutex: std.Thread.Mutex = .{}, +dupes: std.AutoArrayHashMapUnmanaged(SymbolResolver.Index, std.ArrayListUnmanaged(File.Index)) = .{}, +dupes_mutex: std.Thread.Mutex = .{}, dyld_info_cmd: macho.dyld_info_command = .{}, symtab_cmd: macho.symtab_command = .{}, @@ -93,9 +95,10 @@ debug_str_sect_index: ?u8 = null, debug_aranges_sect_index: ?u8 = null, debug_line_sect_index: ?u8 = null, -has_tlv: bool = false, -binds_to_weak: bool = false, -weak_defines: bool = false, +has_tlv: AtomicBool = AtomicBool.init(false), +binds_to_weak: AtomicBool = AtomicBool.init(false), +weak_defines: AtomicBool = AtomicBool.init(false), +has_errors: AtomicBool = AtomicBool.init(false), /// Options /// SDK layout @@ -305,20 +308,15 @@ pub fn deinit(self: *MachO) void { self.sections.deinit(gpa); self.resolver.deinit(gpa); - { - var it = self.undefs.iterator(); - while (it.next()) |entry| { - entry.value_ptr.deinit(gpa); - } - self.undefs.deinit(gpa); + + for (self.undefs.values()) |*val| { + val.deinit(gpa); } - { - var it = self.dupes.iterator(); - while (it.next()) |entry| { - entry.value_ptr.deinit(gpa); - } - self.dupes.deinit(gpa); + self.undefs.deinit(gpa); + for (self.dupes.values()) |*val| { + val.deinit(gpa); } + self.dupes.deinit(gpa); self.symtab.deinit(gpa); self.strtab.deinit(gpa); @@ -395,17 +393,11 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n } for (positionals.items) |obj| { - self.parsePositional(obj.path, obj.must_link) catch |err| switch (err) { - error.MalformedObject, - error.MalformedArchive, - error.MalformedDylib, - error.InvalidCpuArch, - error.InvalidTarget, - => continue, // already reported - error.UnknownFileType => try self.reportParseError(obj.path, "unknown file type for an object file", .{}), + self.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err| switch (err) { + error.UnknownFileType => try self.reportParseError(obj.path, "unknown file type for an input file", .{}), else => |e| try self.reportParseError( obj.path, - "unexpected error: parsing input file failed with error {s}", + "unexpected error: reading input file failed with error {s}", .{@errorName(e)}, ), }; @@ -448,15 +440,11 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n }; for (system_libs.items) |lib| { - self.parseLibrary(lib, false) catch |err| switch (err) { - error.MalformedArchive, - error.MalformedDylib, - error.InvalidCpuArch, - => continue, // already reported - error.UnknownFileType => try self.reportParseError(lib.path, "unknown file type for a library", .{}), + self.classifyInputFile(lib.path, lib, false) catch |err| switch (err) { + error.UnknownFileType => try self.reportParseError(lib.path, "unknown file type for an input file", .{}), else => |e| try self.reportParseError( lib.path, - "unexpected error: parsing library failed with error {s}", + "unexpected error: parsing input file failed with error {s}", .{@errorName(e)}, ), }; @@ -469,13 +457,8 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n break :blk null; }; if (compiler_rt_path) |path| { - self.parsePositional(path, false) catch |err| switch (err) { - error.MalformedObject, - error.MalformedArchive, - error.InvalidCpuArch, - error.InvalidTarget, - => {}, // already reported - error.UnknownFileType => try self.reportParseError(path, "unknown file type for a library", .{}), + self.classifyInputFile(path, .{ .path = path }, false) catch |err| switch (err) { + error.UnknownFileType => try self.reportParseError(path, "unknown file type for an input file", .{}), else => |e| try self.reportParseError( path, "unexpected error: parsing input file failed with error {s}", @@ -484,30 +467,18 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n }; } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + try self.parseInputFiles(); + self.parseDependentDylibs() catch |err| { + switch (err) { + error.MissingLibraryDependencies => {}, + else => |e| try self.reportUnexpectedError( + "unexpected error while parsing dependent libraries: {s}", + .{@errorName(e)}, + ), + } + }; - for (self.dylibs.items) |index| { - self.getFile(index).?.dylib.umbrella = index; - } - - if (self.dylibs.items.len > 0) { - self.parseDependentDylibs() catch |err| { - switch (err) { - error.MissingLibraryDependencies => {}, - else => |e| try self.reportUnexpectedError( - "unexpected error while parsing dependent libraries: {s}", - .{@errorName(e)}, - ), - } - return error.FlushFailure; - }; - } - - for (self.dylibs.items) |index| { - const dylib = self.getFile(index).?.dylib; - if (!dylib.explicit and !dylib.hoisted) continue; - try dylib.initSymbols(self); - } + if (self.base.hasErrors()) return error.FlushFailure; { const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); @@ -579,12 +550,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n else => |e| return e, }; } - self.writeSectionsAndUpdateLinkeditSizes() catch |err| { - switch (err) { - error.ResolveFailed => return error.FlushFailure, - else => |e| return e, - } - }; + try self.writeSectionsAndUpdateLinkeditSizes(); try self.writeSectionsToFile(); try self.allocateLinkeditSegment(); @@ -841,181 +807,186 @@ pub fn resolveLibSystem( }); } -pub const ParseError = error{ - MalformedObject, - MalformedArchive, - MalformedDylib, - MalformedTbd, - NotLibStub, - InvalidCpuArch, - InvalidTarget, - InvalidTargetFatLibrary, - IncompatibleDylibVersion, - OutOfMemory, - Overflow, - InputOutput, - EndOfStream, - FileSystem, - NotSupported, - Unhandled, - UnknownFileType, -} || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError || tapi.TapiError; - -pub fn parsePositional(self: *MachO, path: []const u8, must_link: bool) ParseError!void { +pub fn classifyInputFile(self: *MachO, path: []const u8, lib: SystemLib, must_link: bool) !void { const tracy = trace(@src()); defer tracy.end(); - if (try Object.isObject(path)) { - try self.parseObject(path); - } else { - try self.parseLibrary(.{ .path = path }, must_link); + + log.debug("classifying input file {s}", .{path}); + + const file = try std.fs.cwd().openFile(path, .{}); + const fh = try self.addFileHandle(file); + var buffer: [Archive.SARMAG]u8 = undefined; + + const fat_arch: ?fat.Arch = try self.parseFatFile(file, path); + const offset = if (fat_arch) |fa| fa.offset else 0; + + if (readMachHeader(file, offset) catch null) |h| blk: { + if (h.magic != macho.MH_MAGIC_64) break :blk; + switch (h.filetype) { + macho.MH_OBJECT => try self.addObject(path, fh, offset), + macho.MH_DYLIB => _ = try self.addDylib(lib, true, fh, offset), + else => return error.UnknownFileType, + } + return; + } + if (readArMagic(file, offset, &buffer) catch null) |ar_magic| blk: { + if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk; + try self.addArchive(lib, must_link, fh, fat_arch); + return; } + _ = try self.addTbd(lib, true, fh); } -fn parseLibrary(self: *MachO, lib: SystemLib, must_link: bool) ParseError!void { - const tracy = trace(@src()); - defer tracy.end(); - if (try fat.isFatLibrary(lib.path)) { - const fat_arch = try self.parseFatLibrary(lib.path); - if (try Archive.isArchive(lib.path, fat_arch)) { - try self.parseArchive(lib, must_link, fat_arch); - } else if (try Dylib.isDylib(lib.path, fat_arch)) { - _ = try self.parseDylib(lib, true, fat_arch); - } else return error.UnknownFileType; - } else if (try Archive.isArchive(lib.path, null)) { - try self.parseArchive(lib, must_link, null); - } else if (try Dylib.isDylib(lib.path, null)) { - _ = try self.parseDylib(lib, true, null); - } else { - _ = self.parseTbd(lib, true) catch |err| switch (err) { - error.MalformedTbd => return error.UnknownFileType, - else => |e| return e, - }; +fn parseFatFile(self: *MachO, file: std.fs.File, path: []const u8) !?fat.Arch { + const fat_h = fat.readFatHeader(file) catch return null; + if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null; + var fat_archs_buffer: [2]fat.Arch = undefined; + const fat_archs = try fat.parseArchs(file, fat_h, &fat_archs_buffer); + const cpu_arch = self.getTarget().cpu.arch; + for (fat_archs) |arch| { + if (arch.tag == cpu_arch) return arch; } + try self.reportParseError(path, "missing arch in universal file: expected {s}", .{ + @tagName(cpu_arch), + }); + return error.MissingCpuArch; +} + +pub fn readMachHeader(file: std.fs.File, offset: usize) !macho.mach_header_64 { + var buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; + const nread = try file.preadAll(&buffer, offset); + if (nread != buffer.len) return error.InputOutput; + const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*; + return hdr; +} + +pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { + const nread = try file.preadAll(buffer, offset); + if (nread != buffer.len) return error.InputOutput; + return buffer[0..Archive.SARMAG]; } -fn parseObject(self: *MachO, path: []const u8) ParseError!void { +fn addObject(self: *MachO, path: []const u8, handle: File.HandleIndex, offset: u64) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; - const file = try fs.cwd().openFile(path, .{}); - const handle = try self.addFileHandle(file); const mtime: u64 = mtime: { + const file = self.getFileHandle(handle); const stat = file.stat() catch break :mtime 0; break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); }; const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); - self.files.set(index, .{ - .object = .{ - .offset = 0, // TODO FAT objects - .path = try gpa.dupe(u8, path), - .file_handle = handle, - .mtime = mtime, - .index = index, - }, - }); + self.files.set(index, .{ .object = .{ + .offset = offset, + .path = try gpa.dupe(u8, path), + .file_handle = handle, + .mtime = mtime, + .index = index, + } }); try self.objects.append(gpa, index); - - const object = self.getFile(index).?.object; - try object.parse(self); } -pub fn parseFatLibrary(self: *MachO, path: []const u8) !fat.Arch { - var buffer: [2]fat.Arch = undefined; - const fat_archs = try fat.parseArchs(path, &buffer); - const cpu_arch = self.getTarget().cpu.arch; - for (fat_archs) |arch| { - if (arch.tag == cpu_arch) return arch; +pub fn parseInputFiles(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const tp = self.base.comp.thread_pool; + var wg: WaitGroup = .{}; + + { + wg.reset(); + defer wg.wait(); + + for (self.objects.items) |index| { + tp.spawnWg(&wg, parseInputFileWorker, .{ self, self.getFile(index).? }); + } + for (self.dylibs.items) |index| { + tp.spawnWg(&wg, parseInputFileWorker, .{ self, self.getFile(index).? }); + } } - try self.reportParseError(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)}); - return error.InvalidCpuArch; + + if (self.has_errors.swap(false, .seq_cst)) return error.FlushFailure; +} + +fn parseInputFileWorker(self: *MachO, file: File) void { + file.parse(self) catch |err| { + switch (err) { + error.MalformedObject, + error.MalformedDylib, + error.MalformedTbd, + error.InvalidCpuArch, + error.InvalidTarget, + => {}, // already reported + else => |e| self.reportParseError2(file.getIndex(), "unexpected error: parsing input file failed with error {s}", .{@errorName(e)}) catch {}, + } + _ = self.has_errors.swap(true, .seq_cst); + }; } -fn parseArchive(self: *MachO, lib: SystemLib, must_link: bool, fat_arch: ?fat.Arch) ParseError!void { +fn addArchive(self: *MachO, lib: SystemLib, must_link: bool, handle: File.HandleIndex, fat_arch: ?fat.Arch) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; - const file = try fs.cwd().openFile(lib.path, .{}); - const handle = try self.addFileHandle(file); - var archive = Archive{}; defer archive.deinit(gpa); - try archive.parse(self, lib.path, handle, fat_arch); + try archive.unpack(self, lib.path, handle, fat_arch); - var has_parse_error = false; - for (archive.objects.items) |extracted| { - const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); - self.files.set(index, .{ .object = extracted }); + for (archive.objects.items) |unpacked| { + const index: File.Index = @intCast(try self.files.addOne(gpa)); + self.files.set(index, .{ .object = unpacked }); const object = &self.files.items(.data)[index].object; object.index = index; object.alive = must_link or lib.needed; // TODO: or self.options.all_load; object.hidden = lib.hidden; - object.parse(self) catch |err| switch (err) { - error.MalformedObject, - error.InvalidCpuArch, - error.InvalidTarget, - => has_parse_error = true, - else => |e| return e, - }; try self.objects.append(gpa, index); - - // Finally, we do a post-parse check for -ObjC to see if we need to force load this member - // anyhow. - object.alive = object.alive or (self.force_load_objc and object.hasObjc()); } - if (has_parse_error) return error.MalformedArchive; } -fn parseDylib(self: *MachO, lib: SystemLib, explicit: bool, fat_arch: ?fat.Arch) ParseError!File.Index { +fn addDylib(self: *MachO, lib: SystemLib, explicit: bool, handle: File.HandleIndex, offset: u64) !File.Index { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; - const file = try fs.cwd().openFile(lib.path, .{}); - defer file.close(); - - const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); + const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .dylib = .{ + .offset = offset, + .file_handle = handle, + .tag = .dylib, .path = try gpa.dupe(u8, lib.path), .index = index, .needed = lib.needed, .weak = lib.weak, .reexport = lib.reexport, .explicit = explicit, + .umbrella = index, } }); - const dylib = &self.files.items(.data)[index].dylib; - try dylib.parse(self, file, fat_arch); - try self.dylibs.append(gpa, index); return index; } -fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index { +fn addTbd(self: *MachO, lib: SystemLib, explicit: bool, handle: File.HandleIndex) !File.Index { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; - const file = try fs.cwd().openFile(lib.path, .{}); - defer file.close(); - - var lib_stub = LibStub.loadFromFile(gpa, file) catch return error.MalformedTbd; // TODO actually handle different errors - defer lib_stub.deinit(); - - const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); + const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .dylib = .{ + .offset = 0, + .file_handle = handle, + .tag = .tbd, .path = try gpa.dupe(u8, lib.path), .index = index, .needed = lib.needed, .weak = lib.weak, .reexport = lib.reexport, .explicit = explicit, + .umbrella = index, } }); - const dylib = &self.files.items(.data)[index].dylib; - try dylib.parseTbd(self.getTarget().cpu.arch, self.platform, lib_stub, self); try self.dylibs.append(gpa, index); return index; @@ -1092,6 +1063,8 @@ fn parseDependentDylibs(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); + if (self.dylibs.items.len == 0) return; + const gpa = self.base.comp.gpa; const lib_dirs = self.lib_dirs; const framework_dirs = self.framework_dirs; @@ -1108,7 +1081,7 @@ fn parseDependentDylibs(self: *MachO) !void { while (index < self.dylibs.items.len) : (index += 1) { const dylib_index = self.dylibs.items[index]; - var dependents = std.ArrayList(struct { id: Dylib.Id, file: File.Index }).init(gpa); + var dependents = std.ArrayList(File.Index).init(gpa); defer dependents.deinit(); try dependents.ensureTotalCapacityPrecise(self.getFile(dylib_index).?.dylib.dependents.items.len); @@ -1199,38 +1172,34 @@ fn parseDependentDylibs(self: *MachO) !void { .path = full_path, .weak = is_weak, }; + const file = try std.fs.cwd().openFile(lib.path, .{}); + const fh = try self.addFileHandle(file); + const fat_arch = try self.parseFatFile(file, lib.path); + const offset = if (fat_arch) |fa| fa.offset else 0; const file_index = file_index: { - if (try fat.isFatLibrary(lib.path)) { - const fat_arch = try self.parseFatLibrary(lib.path); - if (try Dylib.isDylib(lib.path, fat_arch)) { - break :file_index try self.parseDylib(lib, false, fat_arch); - } else break :file_index @as(File.Index, 0); - } else if (try Dylib.isDylib(lib.path, null)) { - break :file_index try self.parseDylib(lib, false, null); - } else { - const file_index = self.parseTbd(lib, false) catch |err| switch (err) { - error.MalformedTbd => @as(File.Index, 0), - else => |e| return e, - }; - break :file_index file_index; + if (readMachHeader(file, offset) catch null) |h| blk: { + if (h.magic != macho.MH_MAGIC_64) break :blk; + switch (h.filetype) { + macho.MH_DYLIB => break :file_index try self.addDylib(lib, false, fh, offset), + else => break :file_index @as(File.Index, 0), + } } + break :file_index try self.addTbd(lib, false, fh); }; - dependents.appendAssumeCapacity(.{ .id = id, .file = file_index }); + dependents.appendAssumeCapacity(file_index); } const dylib = self.getFile(dylib_index).?.dylib; - for (dependents.items) |entry| { - const id = entry.id; - const file_index = entry.file; + for (dylib.dependents.items, dependents.items) |id, file_index| { if (self.getFile(file_index)) |file| { const dep_dylib = file.dylib; + try dep_dylib.parse(self); // TODO in parallel dep_dylib.hoisted = self.isHoisted(id.name); - if (self.getFile(dep_dylib.umbrella) == null) { - dep_dylib.umbrella = dylib.umbrella; - } + dep_dylib.umbrella = dylib.umbrella; if (!dep_dylib.hoisted) { const umbrella = dep_dylib.getUmbrella(self); for (dep_dylib.exports.items(.name), dep_dylib.exports.items(.flags)) |off, flags| { + // TODO rethink this entire algorithm try umbrella.addExport(gpa, dep_dylib.getString(off), flags); } try umbrella.rpaths.ensureUnusedCapacity(gpa, dep_dylib.rpaths.keys().len); @@ -1238,15 +1207,13 @@ fn parseDependentDylibs(self: *MachO) !void { umbrella.rpaths.putAssumeCapacity(try gpa.dupe(u8, rpath), {}); } } - } else { - try self.reportDependencyError( - dylib.getUmbrella(self).index, - id.name, - "unable to resolve dependency", - .{}, - ); - has_errors = true; - } + } else try self.reportDependencyError( + dylib.getUmbrella(self).index, + id.name, + "unable to resolve dependency", + .{}, + ); + has_errors = true; } } @@ -1311,95 +1278,51 @@ fn markLive(self: *MachO) void { if (self.getInternalObject()) |obj| obj.markLive(self); } -fn resolveSyntheticSymbols(self: *MachO) !void { - const internal = self.getInternalObject() orelse return; - - if (!self.base.isDynLib()) { - self.mh_execute_header_index = try internal.addSymbol("__mh_execute_header", self); - const sym = self.getSymbol(self.mh_execute_header_index.?); - sym.flags.@"export" = true; - sym.flags.dyn_ref = true; - sym.visibility = .global; - } else { - self.mh_dylib_header_index = try internal.addSymbol("__mh_dylib_header", self); - } - - self.dso_handle_index = try internal.addSymbol("___dso_handle", self); - self.dyld_private_index = try internal.addSymbol("dyld_private", self); - - { - const gpa = self.base.comp.gpa; - var boundary_symbols = std.AutoHashMap(Symbol.Index, void).init(gpa); - defer boundary_symbols.deinit(); - - for (self.objects.items) |index| { - const object = self.getFile(index).?.object; - for (object.symbols.items, 0..) |sym_index, i| { - const nlist = object.symtab.items(.nlist)[i]; - const name = self.getSymbol(sym_index).getName(self); - if (!nlist.undf() or !nlist.ext()) continue; - if (mem.startsWith(u8, name, "segment$start$") or - mem.startsWith(u8, name, "segment$stop$") or - mem.startsWith(u8, name, "section$start$") or - mem.startsWith(u8, name, "section$stop$")) - { - _ = try boundary_symbols.put(sym_index, {}); - } - } - } - - try self.boundary_symbols.ensureTotalCapacityPrecise(gpa, boundary_symbols.count()); - - var it = boundary_symbols.iterator(); - while (it.next()) |entry| { - _ = try internal.addSymbol(self.getSymbol(entry.key_ptr.*).getName(self), self); - self.boundary_symbols.appendAssumeCapacity(entry.key_ptr.*); - } - } -} - fn convertTentativeDefsAndResolveSpecialSymbols(self: *MachO) !void { - for (self.objects.items) |index| { - try self.getFile(index).?.object.convertTentativeDefinitions(self); - } - if (self.getInternalObject()) |obj| { - try obj.resolveBoundarySymbols(self); - try obj.resolveObjcMsgSendSymbols(self); + const tp = self.base.comp.thread_pool; + var wg: WaitGroup = .{}; + { + wg.reset(); + defer wg.wait(); + for (self.objects.items) |index| { + tp.spawnWg(&wg, convertTentativeDefinitionsWorker, .{ self, self.getFile(index).?.object }); + } + if (self.getInternalObject()) |obj| { + tp.spawnWg(&wg, resolveSpecialSymbolsWorker, .{ self, obj }); + } } + if (self.has_errors.swap(false, .seq_cst)) return error.FlushFailure; } -fn createObjcSections(self: *MachO) !void { - const gpa = self.base.comp.gpa; - var objc_msgsend_syms = std.AutoArrayHashMap(Symbol.Index, void).init(gpa); - defer objc_msgsend_syms.deinit(); - - for (self.objects.items) |index| { - const object = self.getFile(index).?.object; - - for (object.symbols.items, 0..) |sym_index, i| { - const nlist_idx = @as(Symbol.Index, @intCast(i)); - const nlist = object.symtab.items(.nlist)[nlist_idx]; - if (!nlist.ext()) continue; - if (!nlist.undf()) continue; - - const sym = self.getSymbol(sym_index); - if (sym.getFile(self) != null) continue; - if (mem.startsWith(u8, sym.getName(self), "_objc_msgSend$")) { - _ = try objc_msgsend_syms.put(sym_index, {}); - } - } - } +fn convertTentativeDefinitionsWorker(self: *MachO, object: *Object) void { + const tracy = trace(@src()); + defer tracy.end(); + object.convertTentativeDefinitions(self) catch |err| { + self.reportParseError2( + object.index, + "unexpected error occurred while converting tentative symbols into defined symbols: {s}", + .{@errorName(err)}, + ) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; +} - for (objc_msgsend_syms.keys()) |sym_index| { - const internal = self.getInternalObject().?; - const sym = self.getSymbol(sym_index); - _ = try internal.addSymbol(sym.getName(self), self); - sym.visibility = .hidden; - const name = eatPrefix(sym.getName(self), "_objc_msgSend$").?; - const selrefs_index = try internal.addObjcMsgsendSections(name, self); - try sym.addExtra(.{ .objc_selrefs = selrefs_index }, self); - sym.flags.objc_stubs = true; - } +fn resolveSpecialSymbolsWorker(self: *MachO, obj: *InternalObject) void { + const tracy = trace(@src()); + defer tracy.end(); + obj.resolveBoundarySymbols(self) catch |err| { + self.reportUnexpectedError("unexpected error occurred while resolving boundary symbols: {s}", .{ + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + return; + }; + obj.resolveObjcMsgSendSymbols(self) catch |err| { + self.reportUnexpectedError("unexpected error occurred while resolving ObjC msgsend stubs: {s}", .{ + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; } pub fn dedupLiterals(self: *MachO) !void { @@ -1420,14 +1343,20 @@ pub fn dedupLiterals(self: *MachO) !void { try object.resolveLiterals(&lp, self); } - if (self.getZigObject()) |zo| { - zo.dedupLiterals(lp, self); - } - for (self.objects.items) |index| { - self.getFile(index).?.object.dedupLiterals(lp, self); - } - if (self.getInternalObject()) |object| { - object.dedupLiterals(lp, self); + const tp = self.base.comp.thread_pool; + var wg: WaitGroup = .{}; + { + wg.reset(); + defer wg.wait(); + if (self.getZigObject()) |zo| { + tp.spawnWg(&wg, File.dedupLiterals, .{ zo.asFile(), lp, self }); + } + for (self.objects.items) |index| { + tp.spawnWg(&wg, File.dedupLiterals, .{ self.getFile(index).?, lp, self }); + } + if (self.getInternalObject()) |object| { + tp.spawnWg(&wg, File.dedupLiterals, .{ object.asFile(), lp, self }); + } } } @@ -1441,18 +1370,41 @@ fn claimUnresolved(self: *MachO) void { } fn checkDuplicates(self: *MachO) !void { - if (self.getZigObject()) |zo| { - try zo.asFile().checkDuplicates(self); - } - for (self.objects.items) |index| { - try self.getFile(index).?.checkDuplicates(self); - } - if (self.getInternalObject()) |obj| { - try obj.asFile().checkDuplicates(self); + const tracy = trace(@src()); + defer tracy.end(); + + const tp = self.base.comp.thread_pool; + var wg: WaitGroup = .{}; + { + wg.reset(); + defer wg.wait(); + if (self.getZigObject()) |zo| { + tp.spawnWg(&wg, checkDuplicatesWorker, .{ self, zo.asFile() }); + } + for (self.objects.items) |index| { + tp.spawnWg(&wg, checkDuplicatesWorker, .{ self, self.getFile(index).? }); + } + if (self.getInternalObject()) |obj| { + tp.spawnWg(&wg, checkDuplicatesWorker, .{ self, obj.asFile() }); + } } + + if (self.has_errors.swap(false, .seq_cst)) return error.FlushFailure; + try self.reportDuplicates(); } +fn checkDuplicatesWorker(self: *MachO, file: File) void { + const tracy = trace(@src()); + defer tracy.end(); + file.checkDuplicates(self) catch |err| { + self.reportParseError2(file.getIndex(), "failed to check for duplicate definitions: {s}", .{ + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; +} + fn markImportsAndExports(self: *MachO) void { const tracy = trace(@src()); defer tracy.end(); @@ -1491,16 +1443,26 @@ fn scanRelocs(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - if (self.getZigObject()) |zo| { - try zo.scanRelocs(self); - } - for (self.objects.items) |index| { - try self.getFile(index).?.object.scanRelocs(self); - } - if (self.getInternalObject()) |obj| { - obj.scanRelocs(self); + const tp = self.base.comp.thread_pool; + var wg: WaitGroup = .{}; + + { + wg.reset(); + defer wg.wait(); + + if (self.getZigObject()) |zo| { + tp.spawnWg(&wg, scanRelocsWorker, .{ self, zo.asFile() }); + } + for (self.objects.items) |index| { + tp.spawnWg(&wg, scanRelocsWorker, .{ self, self.getFile(index).? }); + } + if (self.getInternalObject()) |obj| { + tp.spawnWg(&wg, scanRelocsWorker, .{ self, obj.asFile() }); + } } + if (self.has_errors.swap(false, .seq_cst)) return error.FlushFailure; + try self.reportUndefs(); if (self.getZigObject()) |zo| { @@ -1517,40 +1479,77 @@ fn scanRelocs(self: *MachO) !void { } } +fn scanRelocsWorker(self: *MachO, file: File) void { + file.scanRelocs(self) catch |err| { + self.reportParseError2(file.getIndex(), "failed to scan relocations: {s}", .{ + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; +} + +fn sortGlobalSymbolsByName(self: *MachO, symbols: []SymbolResolver.Index) void { + const lessThan = struct { + fn lessThan(ctx: *MachO, lhs: SymbolResolver.Index, rhs: SymbolResolver.Index) bool { + const lhs_name = ctx.resolver.keys.items[lhs - 1].getName(ctx); + const rhs_name = ctx.resolver.keys.items[rhs - 1].getName(ctx); + return mem.order(u8, lhs_name, rhs_name) == .lt; + } + }.lessThan; + mem.sort(SymbolResolver.Index, symbols, self, lessThan); +} + fn reportUndefs(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); if (self.undefined_treatment == .suppress or self.undefined_treatment == .dynamic_lookup) return; + if (self.undefs.keys().len == 0) return; // Nothing to do + const gpa = self.base.comp.gpa; const max_notes = 4; - var has_undefs = false; - var it = self.undefs.iterator(); - while (it.next()) |entry| { - const undef_sym = self.resolver.keys.items[entry.key_ptr.* - 1]; - const notes = entry.value_ptr.*; + // We will sort by name, and then by file to ensure deterministic output. + var keys = try std.ArrayList(SymbolResolver.Index).initCapacity(gpa, self.undefs.keys().len); + defer keys.deinit(); + keys.appendSliceAssumeCapacity(self.undefs.keys()); + self.sortGlobalSymbolsByName(keys.items); + + const refLessThan = struct { + fn lessThan(ctx: void, lhs: Ref, rhs: Ref) bool { + _ = ctx; + return lhs.lessThan(rhs); + } + }.lessThan; + + for (self.undefs.values()) |*refs| { + mem.sort(Ref, refs.items, {}, refLessThan); + } + + for (keys.items) |key| { + const undef_sym = self.resolver.keys.items[key - 1]; + const notes = self.undefs.get(key).?; const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes); - var err = try self.addErrorWithNotes(nnotes); - try err.addMsg(self, "undefined symbol: {s}", .{undef_sym.getName(self)}); - has_undefs = true; + var err = try self.base.addErrorWithNotes(nnotes); + try err.addMsg("undefined symbol: {s}", .{undef_sym.getName(self)}); var inote: usize = 0; while (inote < @min(notes.items.len, max_notes)) : (inote += 1) { const note = notes.items[inote]; const file = self.getFile(note.file).?; const atom = note.getAtom(self).?; - try err.addNote(self, "referenced by {}:{s}", .{ file.fmtPath(), atom.getName(self) }); + try err.addNote("referenced by {}:{s}", .{ file.fmtPath(), atom.getName(self) }); } if (notes.items.len > max_notes) { const remaining = notes.items.len - max_notes; - try err.addNote(self, "referenced {d} more times", .{remaining}); + try err.addNote("referenced {d} more times", .{remaining}); } } - if (has_undefs) return error.HasUndefinedSymbols; + + return error.HasUndefinedSymbols; } fn initOutputSections(self: *MachO) !void { @@ -1786,7 +1785,7 @@ pub fn sortSections(self: *MachO) !void { if (self.getZigObject()) |zo| { for (zo.getAtoms()) |atom_index| { const atom = zo.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; atom.out_n_sect = backlinks[atom.out_n_sect]; } } @@ -1795,7 +1794,7 @@ pub fn sortSections(self: *MachO) !void { const file = self.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; atom.out_n_sect = backlinks[atom.out_n_sect]; } } @@ -1803,7 +1802,7 @@ pub fn sortSections(self: *MachO) !void { if (self.getInternalObject()) |object| { for (object.getAtoms()) |atom_index| { const atom = object.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; atom.out_n_sect = backlinks[atom.out_n_sect]; } } @@ -1844,7 +1843,7 @@ pub fn addAtomsToSections(self: *MachO) !void { if (self.getZigObject()) |zo| { for (zo.getAtoms()) |atom_index| { const atom = zo.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; if (self.isZigSection(atom.out_n_sect)) continue; const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(gpa, .{ .index = atom_index, .file = zo.index }); @@ -1854,7 +1853,7 @@ pub fn addAtomsToSections(self: *MachO) !void { const file = self.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(gpa, .{ .index = atom_index, .file = index }); } @@ -1862,7 +1861,7 @@ pub fn addAtomsToSections(self: *MachO) !void { if (self.getInternalObject()) |object| { for (object.getAtoms()) |atom_index| { const atom = object.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(gpa, .{ .index = atom_index, .file = object.index }); } @@ -1881,46 +1880,43 @@ fn calcSectionSizes(self: *MachO) !void { header.@"align" = 3; } - const slice = self.sections.slice(); - for (slice.items(.header), slice.items(.atoms)) |*header, atoms| { - if (atoms.items.len == 0) continue; - if (self.requiresThunks() and header.isCode()) continue; - - for (atoms.items) |ref| { - const atom = ref.getAtom(self).?; - const atom_alignment = atom.alignment.toByteUnits() orelse 1; - const offset = mem.alignForward(u64, header.size, atom_alignment); - const padding = offset - header.size; - atom.value = offset; - header.size += padding + atom.size; - header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); - } - } - - if (self.requiresThunks()) { + const tp = self.base.comp.thread_pool; + var wg: WaitGroup = .{}; + { + wg.reset(); + defer wg.wait(); + const slice = self.sections.slice(); for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| { - if (!header.isCode()) continue; if (atoms.items.len == 0) continue; + if (self.requiresThunks() and header.isCode()) continue; + tp.spawnWg(&wg, calcSectionSizeWorker, .{ self, @as(u8, @intCast(i)) }); + } - // Create jump/branch range extenders if needed. - try thunks.createThunks(@intCast(i), self); + if (self.requiresThunks()) { + for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| { + if (!header.isCode()) continue; + if (atoms.items.len == 0) continue; + tp.spawnWg(&wg, createThunksWorker, .{ self, @as(u8, @intCast(i)) }); + } } - } - // At this point, we can also calculate symtab and data-in-code linkedit section sizes - if (self.getZigObject()) |zo| { - zo.asFile().calcSymtabSize(self); - } - for (self.objects.items) |index| { - self.getFile(index).?.calcSymtabSize(self); - } - for (self.dylibs.items) |index| { - self.getFile(index).?.calcSymtabSize(self); - } - if (self.getInternalObject()) |obj| { - obj.asFile().calcSymtabSize(self); + // At this point, we can also calculate symtab and data-in-code linkedit section sizes + if (self.getZigObject()) |zo| { + tp.spawnWg(&wg, File.calcSymtabSize, .{ zo.asFile(), self }); + } + for (self.objects.items) |index| { + tp.spawnWg(&wg, File.calcSymtabSize, .{ self.getFile(index).?, self }); + } + for (self.dylibs.items) |index| { + tp.spawnWg(&wg, File.calcSymtabSize, .{ self.getFile(index).?, self }); + } + if (self.getInternalObject()) |obj| { + tp.spawnWg(&wg, File.calcSymtabSize, .{ obj.asFile(), self }); + } } + if (self.has_errors.swap(false, .seq_cst)) return error.FlushFailure; + try self.calcSymtabSize(); if (self.got_sect_index) |idx| { @@ -1968,6 +1964,49 @@ fn calcSectionSizes(self: *MachO) !void { } } +fn calcSectionSizeWorker(self: *MachO, sect_id: u8) void { + const tracy = trace(@src()); + defer tracy.end(); + const doWork = struct { + fn doWork(macho_file: *MachO, header: *macho.section_64, atoms: []const Ref) !void { + for (atoms) |ref| { + const atom = ref.getAtom(macho_file).?; + const atom_alignment = atom.alignment.toByteUnits() orelse 1; + const offset = mem.alignForward(u64, header.size, atom_alignment); + const padding = offset - header.size; + atom.value = offset; + header.size += padding + atom.size; + header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); + } + } + }.doWork; + const slice = self.sections.slice(); + const header = &slice.items(.header)[sect_id]; + const atoms = slice.items(.atoms)[sect_id].items; + doWork(self, header, atoms) catch |err| { + self.reportUnexpectedError("failed to calculate size of section '{s},{s}': {s}", .{ + header.segName(), + header.sectName(), + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; +} + +fn createThunksWorker(self: *MachO, sect_id: u8) void { + const tracy = trace(@src()); + defer tracy.end(); + thunks.createThunks(sect_id, self) catch |err| { + const header = self.sections.items(.header)[sect_id]; + self.reportUnexpectedError("failed to create thunks and calculate size of section '{s},{s}': {s}", .{ + header.segName(), + header.sectName(), + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; +} + fn generateUnwindInfo(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -2349,6 +2388,9 @@ fn resizeSections(self: *MachO) !void { } fn writeSectionsAndUpdateLinkeditSizes(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + const gpa = self.base.comp.gpa; const cmd = self.symtab_cmd; @@ -2356,64 +2398,98 @@ fn writeSectionsAndUpdateLinkeditSizes(self: *MachO) !void { try self.strtab.resize(gpa, cmd.strsize); self.strtab.items[0] = 0; - for (self.objects.items) |index| { - try self.getFile(index).?.writeAtoms(self); - } - if (self.getZigObject()) |zo| { - try zo.writeAtoms(self); - } - if (self.getInternalObject()) |obj| { - try obj.asFile().writeAtoms(self); - } - for (self.thunks.items) |thunk| { - const out = self.sections.items(.out)[thunk.out_n_sect].items; - const off = math.cast(usize, thunk.value) orelse return error.Overflow; - const size = thunk.size(); - var stream = std.io.fixedBufferStream(out[off..][0..size]); - try thunk.write(self, stream.writer()); - } + const tp = self.base.comp.thread_pool; + var wg: WaitGroup = .{}; + { + wg.reset(); + defer wg.wait(); - const slice = self.sections.slice(); - for (&[_]?u8{ - self.eh_frame_sect_index, - self.unwind_info_sect_index, - self.got_sect_index, - self.stubs_sect_index, - self.la_symbol_ptr_sect_index, - self.tlv_ptr_sect_index, - self.objc_stubs_sect_index, - }) |maybe_sect_id| { - if (maybe_sect_id) |sect_id| { - const out = slice.items(.out)[sect_id].items; - try self.writeSyntheticSection(sect_id, out); + for (self.objects.items) |index| { + tp.spawnWg(&wg, writeAtomsWorker, .{ self, self.getFile(index).? }); + } + if (self.getZigObject()) |zo| { + tp.spawnWg(&wg, writeAtomsWorker, .{ self, zo.asFile() }); + } + if (self.getInternalObject()) |obj| { + tp.spawnWg(&wg, writeAtomsWorker, .{ self, obj.asFile() }); + } + for (self.thunks.items) |thunk| { + tp.spawnWg(&wg, writeThunkWorker, .{ self, thunk }); } - } - if (self.la_symbol_ptr_sect_index) |_| { - try self.updateLazyBindSize(); - } + const slice = self.sections.slice(); + for (&[_]?u8{ + self.eh_frame_sect_index, + self.unwind_info_sect_index, + self.got_sect_index, + self.stubs_sect_index, + self.la_symbol_ptr_sect_index, + self.tlv_ptr_sect_index, + self.objc_stubs_sect_index, + }) |maybe_sect_id| { + if (maybe_sect_id) |sect_id| { + const out = slice.items(.out)[sect_id].items; + tp.spawnWg(&wg, writeSyntheticSectionWorker, .{ self, sect_id, out }); + } + } - try self.rebase.updateSize(self); - try self.bind.updateSize(self); - try self.weak_bind.updateSize(self); - try self.export_trie.updateSize(self); - try self.data_in_code.updateSize(self); + if (self.la_symbol_ptr_sect_index) |_| { + tp.spawnWg(&wg, updateLazyBindSizeWorker, .{self}); + } - if (self.getZigObject()) |zo| { - zo.asFile().writeSymtab(self, self); - } - for (self.objects.items) |index| { - self.getFile(index).?.writeSymtab(self, self); - } - for (self.dylibs.items) |index| { - self.getFile(index).?.writeSymtab(self, self); - } - if (self.getInternalObject()) |obj| { - obj.asFile().writeSymtab(self, self); + tp.spawnWg(&wg, updateLinkeditSizeWorker, .{ self, .rebase }); + tp.spawnWg(&wg, updateLinkeditSizeWorker, .{ self, .bind }); + tp.spawnWg(&wg, updateLinkeditSizeWorker, .{ self, .weak_bind }); + tp.spawnWg(&wg, updateLinkeditSizeWorker, .{ self, .export_trie }); + tp.spawnWg(&wg, updateLinkeditSizeWorker, .{ self, .data_in_code }); + + if (self.getZigObject()) |zo| { + tp.spawnWg(&wg, File.writeSymtab, .{ zo.asFile(), self, self }); + } + for (self.objects.items) |index| { + tp.spawnWg(&wg, File.writeSymtab, .{ self.getFile(index).?, self, self }); + } + for (self.dylibs.items) |index| { + tp.spawnWg(&wg, File.writeSymtab, .{ self.getFile(index).?, self, self }); + } + if (self.getInternalObject()) |obj| { + tp.spawnWg(&wg, File.writeSymtab, .{ obj.asFile(), self, self }); + } } + + if (self.has_errors.swap(false, .seq_cst)) return error.FlushFailure; +} + +fn writeAtomsWorker(self: *MachO, file: File) void { + const tracy = trace(@src()); + defer tracy.end(); + file.writeAtoms(self) catch |err| { + self.reportParseError2(file.getIndex(), "failed to resolve relocations and write atoms: {s}", .{ + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; +} + +fn writeThunkWorker(self: *MachO, thunk: Thunk) void { + const tracy = trace(@src()); + defer tracy.end(); + const doWork = struct { + fn doWork(th: Thunk, buffer: []u8, macho_file: *MachO) !void { + const off = math.cast(usize, th.value) orelse return error.Overflow; + const size = th.size(); + var stream = std.io.fixedBufferStream(buffer[off..][0..size]); + try th.write(macho_file, stream.writer()); + } + }.doWork; + const out = self.sections.items(.out)[thunk.out_n_sect].items; + doWork(thunk, out, self) catch |err| { + self.reportUnexpectedError("failed to write contents of thunk: {s}", .{@errorName(err)}) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; } -fn writeSyntheticSection(self: *MachO, sect_id: u8, out: []u8) !void { +fn writeSyntheticSectionWorker(self: *MachO, sect_id: u8, out: []u8) void { const tracy = trace(@src()); defer tracy.end(); @@ -2427,6 +2503,22 @@ fn writeSyntheticSection(self: *MachO, sect_id: u8, out: []u8) !void { objc_stubs, }; + const doWork = struct { + fn doWork(macho_file: *MachO, tag: Tag, buffer: []u8) !void { + var stream = std.io.fixedBufferStream(buffer); + switch (tag) { + .eh_frame => eh_frame.write(macho_file, buffer), + .unwind_info => try macho_file.unwind_info.write(macho_file, buffer), + .got => try macho_file.got.write(macho_file, stream.writer()), + .stubs => try macho_file.stubs.write(macho_file, stream.writer()), + .la_symbol_ptr => try macho_file.la_symbol_ptr.write(macho_file, stream.writer()), + .tlv_ptr => try macho_file.tlv_ptr.write(macho_file, stream.writer()), + .objc_stubs => try macho_file.objc_stubs.write(macho_file, stream.writer()), + } + } + }.doWork; + + const header = self.sections.items(.header)[sect_id]; const tag: Tag = tag: { if (self.eh_frame_sect_index != null and self.eh_frame_sect_index.? == sect_id) break :tag .eh_frame; @@ -2444,26 +2536,57 @@ fn writeSyntheticSection(self: *MachO, sect_id: u8, out: []u8) !void { self.objc_stubs_sect_index.? == sect_id) break :tag .objc_stubs; unreachable; }; - var stream = std.io.fixedBufferStream(out); - switch (tag) { - .eh_frame => eh_frame.write(self, out), - .unwind_info => try self.unwind_info.write(self, out), - .got => try self.got.write(self, stream.writer()), - .stubs => try self.stubs.write(self, stream.writer()), - .la_symbol_ptr => try self.la_symbol_ptr.write(self, stream.writer()), - .tlv_ptr => try self.tlv_ptr.write(self, stream.writer()), - .objc_stubs => try self.objc_stubs.write(self, stream.writer()), - } + doWork(self, tag, out) catch |err| { + self.reportUnexpectedError("could not write section '{s},{s}': {s}", .{ + header.segName(), + header.sectName(), + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; } -fn updateLazyBindSize(self: *MachO) !void { +fn updateLazyBindSizeWorker(self: *MachO) void { const tracy = trace(@src()); defer tracy.end(); - try self.lazy_bind.updateSize(self); - const sect_id = self.stubs_helper_sect_index.?; - const out = &self.sections.items(.out)[sect_id]; - var stream = std.io.fixedBufferStream(out.items); - try self.stubs_helper.write(self, stream.writer()); + const doWork = struct { + fn doWork(macho_file: *MachO) !void { + try macho_file.lazy_bind.updateSize(macho_file); + const sect_id = macho_file.stubs_helper_sect_index.?; + const out = &macho_file.sections.items(.out)[sect_id]; + var stream = std.io.fixedBufferStream(out.items); + try macho_file.stubs_helper.write(macho_file, stream.writer()); + } + }.doWork; + doWork(self) catch |err| { + self.reportUnexpectedError("could not calculate size of lazy binding section: {s}", .{ + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; +} + +pub fn updateLinkeditSizeWorker(self: *MachO, tag: enum { + rebase, + bind, + weak_bind, + export_trie, + data_in_code, +}) void { + const res = switch (tag) { + .rebase => self.rebase.updateSize(self), + .bind => self.bind.updateSize(self), + .weak_bind => self.weak_bind.updateSize(self), + .export_trie => self.export_trie.updateSize(self), + .data_in_code => self.data_in_code.updateSize(self), + }; + res catch |err| { + self.reportUnexpectedError("could not calculate size of {s} section: {s}", .{ + @tagName(tag), + @errorName(err), + }) catch {}; + _ = self.has_errors.swap(true, .seq_cst); + }; } fn writeSectionsToFile(self: *MachO) !void { @@ -2791,13 +2914,13 @@ fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void { header.flags |= macho.MH_NO_REEXPORTED_DYLIBS; } - if (self.has_tlv) { + if (self.has_tlv.load(.seq_cst)) { header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; } - if (self.binds_to_weak) { + if (self.binds_to_weak.load(.seq_cst)) { header.flags |= macho.MH_BINDS_TO_WEAK; } - if (self.weak_defines) { + if (self.weak_defines.load(.seq_cst)) { header.flags |= macho.MH_WEAK_DEFINES; } @@ -3323,13 +3446,13 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo const mem_capacity = self.allocatedSizeVirtual(seg.vmaddr); if (needed_size > mem_capacity) { - var err = try self.addErrorWithNotes(2); - try err.addMsg(self, "fatal linker error: cannot expand segment seg({d})({s}) in virtual memory", .{ + var err = try self.base.addErrorWithNotes(2); + try err.addMsg("fatal linker error: cannot expand segment seg({d})({s}) in virtual memory", .{ seg_id, seg.segName(), }); - try err.addNote(self, "TODO: emit relocations to memory locations in self-hosted backends", .{}); - try err.addNote(self, "as a workaround, try increasing pre-allocated virtual memory of each segment", .{}); + try err.addNote("TODO: emit relocations to memory locations in self-hosted backends", .{}); + try err.addNote("as a workaround, try increasing pre-allocated virtual memory of each segment", .{}); } seg.vmsize = needed_size; @@ -3394,6 +3517,8 @@ pub fn getTarget(self: MachO) std.Target { /// the original file. This is super messy, but there doesn't seem any other /// way to please the XNU. pub fn invalidateKernelCache(dir: fs.Dir, sub_path: []const u8) !void { + const tracy = trace(@src()); + defer tracy.end(); if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) { try dir.copyFile(sub_path, dir, sub_path, .{}); } @@ -3618,65 +3743,15 @@ pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 { return null; } -const ErrorWithNotes = struct { - /// Allocated index in comp.link_errors array. - index: usize, - - /// Next available note slot. - note_slot: usize = 0, - - pub fn addMsg( - err: ErrorWithNotes, - macho_file: *MachO, - comptime format: []const u8, - args: anytype, - ) error{OutOfMemory}!void { - const comp = macho_file.base.comp; - const gpa = comp.gpa; - const err_msg = &comp.link_errors.items[err.index]; - err_msg.msg = try std.fmt.allocPrint(gpa, format, args); - } - - pub fn addNote( - err: *ErrorWithNotes, - macho_file: *MachO, - comptime format: []const u8, - args: anytype, - ) error{OutOfMemory}!void { - const comp = macho_file.base.comp; - const gpa = comp.gpa; - const err_msg = &comp.link_errors.items[err.index]; - assert(err.note_slot < err_msg.notes.len); - err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) }; - err.note_slot += 1; - } -}; - -pub fn addErrorWithNotes(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes { - const comp = self.base.comp; - const gpa = comp.gpa; - try comp.link_errors.ensureUnusedCapacity(gpa, 1); - return self.addErrorWithNotesAssumeCapacity(note_count); -} - -fn addErrorWithNotesAssumeCapacity(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes { - const comp = self.base.comp; - const gpa = comp.gpa; - const index = comp.link_errors.items.len; - const err = comp.link_errors.addOneAssumeCapacity(); - err.* = .{ .msg = undefined, .notes = try gpa.alloc(link.File.ErrorMsg, note_count) }; - return .{ .index = index }; -} - pub fn reportParseError( self: *MachO, path: []const u8, comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, format, args); - try err.addNote(self, "while parsing {s}", .{path}); + var err = try self.base.addErrorWithNotes(1); + try err.addMsg(format, args); + try err.addNote("while parsing {s}", .{path}); } pub fn reportParseError2( @@ -3685,9 +3760,9 @@ pub fn reportParseError2( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, format, args); - try err.addNote(self, "while parsing {}", .{self.getFile(file_index).?.fmtPath()}); + var err = try self.base.addErrorWithNotes(1); + try err.addMsg(format, args); + try err.addNote("while parsing {}", .{self.getFile(file_index).?.fmtPath()}); } fn reportMissingLibraryError( @@ -3696,10 +3771,10 @@ fn reportMissingLibraryError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(checked_paths.len); - try err.addMsg(self, format, args); + var err = try self.base.addErrorWithNotes(checked_paths.len); + try err.addMsg(format, args); for (checked_paths) |path| { - try err.addNote(self, "tried {s}", .{path}); + try err.addNote("tried {s}", .{path}); } } @@ -3711,12 +3786,12 @@ fn reportMissingDependencyError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(2 + checked_paths.len); - try err.addMsg(self, format, args); - try err.addNote(self, "while resolving {s}", .{path}); - try err.addNote(self, "a dependency of {}", .{self.getFile(parent).?.fmtPath()}); + var err = try self.base.addErrorWithNotes(2 + checked_paths.len); + try err.addMsg(format, args); + try err.addNote("while resolving {s}", .{path}); + try err.addNote("a dependency of {}", .{self.getFile(parent).?.fmtPath()}); for (checked_paths) |p| { - try err.addNote(self, "tried {s}", .{p}); + try err.addNote("tried {s}", .{p}); } } @@ -3727,48 +3802,58 @@ fn reportDependencyError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(2); - try err.addMsg(self, format, args); - try err.addNote(self, "while parsing {s}", .{path}); - try err.addNote(self, "a dependency of {}", .{self.getFile(parent).?.fmtPath()}); + var err = try self.base.addErrorWithNotes(2); + try err.addMsg(format, args); + try err.addNote("while parsing {s}", .{path}); + try err.addNote("a dependency of {}", .{self.getFile(parent).?.fmtPath()}); } pub fn reportUnexpectedError(self: *MachO, comptime format: []const u8, args: anytype) error{OutOfMemory}!void { - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, format, args); - try err.addNote(self, "please report this as a linker bug on https://github.com/ziglang/zig/issues/new/choose", .{}); + var err = try self.base.addErrorWithNotes(1); + try err.addMsg(format, args); + try err.addNote("please report this as a linker bug on https://github.com/ziglang/zig/issues/new/choose", .{}); } fn reportDuplicates(self: *MachO) error{ HasDuplicates, OutOfMemory }!void { const tracy = trace(@src()); defer tracy.end(); + if (self.dupes.keys().len == 0) return; // Nothing to do + + const gpa = self.base.comp.gpa; const max_notes = 3; - var has_dupes = false; - var it = self.dupes.iterator(); - while (it.next()) |entry| { - const sym = self.resolver.keys.items[entry.key_ptr.* - 1]; - const notes = entry.value_ptr.*; + // We will sort by name, and then by file to ensure deterministic output. + var keys = try std.ArrayList(SymbolResolver.Index).initCapacity(gpa, self.dupes.keys().len); + defer keys.deinit(); + keys.appendSliceAssumeCapacity(self.dupes.keys()); + self.sortGlobalSymbolsByName(keys.items); + + for (self.dupes.values()) |*refs| { + mem.sort(File.Index, refs.items, {}, std.sort.asc(File.Index)); + } + + for (keys.items) |key| { + const sym = self.resolver.keys.items[key - 1]; + const notes = self.dupes.get(key).?; const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes); - var err = try self.addErrorWithNotes(nnotes + 1); - try err.addMsg(self, "duplicate symbol definition: {s}", .{sym.getName(self)}); - try err.addNote(self, "defined by {}", .{sym.getFile(self).?.fmtPath()}); - has_dupes = true; + var err = try self.base.addErrorWithNotes(nnotes + 1); + try err.addMsg("duplicate symbol definition: {s}", .{sym.getName(self)}); + try err.addNote("defined by {}", .{sym.getFile(self).?.fmtPath()}); var inote: usize = 0; while (inote < @min(notes.items.len, max_notes)) : (inote += 1) { const file = self.getFile(notes.items[inote]).?; - try err.addNote(self, "defined by {}", .{file.fmtPath()}); + try err.addNote("defined by {}", .{file.fmtPath()}); } if (notes.items.len > max_notes) { const remaining = notes.items.len - max_notes; - try err.addNote(self, "defined {d} more times", .{remaining}); + try err.addNote("defined {d} more times", .{remaining}); } } - if (has_dupes) return error.HasDuplicates; + return error.HasDuplicates; } pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols { @@ -4367,6 +4452,13 @@ pub const Ref = struct { return ref.index == other.index and ref.file == other.file; } + pub fn lessThan(ref: Ref, other: Ref) bool { + if (ref.file == other.file) { + return ref.index < other.index; + } + return ref.file < other.file; + } + pub fn getFile(ref: Ref, macho_file: *MachO) ?File { return macho_file.getFile(ref.file); } @@ -4487,6 +4579,11 @@ pub const SymbolResolver = struct { pub const Index = u32; }; +pub const String = struct { + pos: u32 = 0, + len: u32 = 0, +}; + const MachO = @This(); const std = @import("std"); @@ -4523,6 +4620,7 @@ const Alignment = Atom.Alignment; const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); pub const Atom = @import("MachO/Atom.zig"); +const AtomicBool = std.atomic.Value(bool); const Bind = bind.Bind; const Cache = std.Build.Cache; const CodeSignature = @import("MachO/CodeSignature.zig"); @@ -4540,7 +4638,6 @@ const ObjcStubsSection = synthetic.ObjcStubsSection; const Object = @import("MachO/Object.zig"); const LazyBind = bind.LazyBind; const LaSymbolPtrSection = synthetic.LaSymbolPtrSection; -const LibStub = tapi.LibStub; const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; const Md5 = std.crypto.hash.Md5; @@ -4558,6 +4655,7 @@ const Thunk = thunks.Thunk; const TlvPtrSection = synthetic.TlvPtrSection; const Value = @import("../Value.zig"); const UnwindInfo = @import("MachO/UnwindInfo.zig"); +const WaitGroup = std.Thread.WaitGroup; const WeakBind = bind.WeakBind; const ZigGotSection = synthetic.ZigGotSection; const ZigObject = @import("MachO/ZigObject.zig"); diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index c478a9bef795eb2a2bb8a6ea10eb064c768a7efc..8cb28b5f295d977604a21b109f90b116bd38d759 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -1,21 +1,10 @@ objects: std.ArrayListUnmanaged(Object) = .{}, -pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - if (fat_arch) |arch| { - try file.seekTo(arch.offset); - } - const magic = file.reader().readBytesNoEof(SARMAG) catch return false; - if (!mem.eql(u8, &magic, ARMAG)) return false; - return true; -} - pub fn deinit(self: *Archive, allocator: Allocator) void { self.objects.deinit(allocator); } -pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { +pub fn unpack(self: *Archive, macho_file: *MachO, path: []const u8, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { const gpa = macho_file.base.comp.gpa; var arena = std.heap.ArenaAllocator.init(gpa); diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index f82bf9970083cbe6a539d0312103c4ea5bc58026..8a47a30264b1b2045c9b8c78f5d0398a4dc75c70 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -2,7 +2,7 @@ value: u64 = 0, /// Name of this Atom. -name: u32 = 0, +name: MachO.String = .{}, /// Index into linker's input file table. file: File.Index = 0, @@ -26,7 +26,11 @@ off: u64 = 0, /// Index of this atom in the linker's atoms table. atom_index: Index = 0, -flags: Flags = .{}, +/// Specifies whether this atom is alive or has been garbage collected. +alive: AtomicBool = AtomicBool.init(true), + +/// Specifies if this atom has been visited during garbage collection. +visited: AtomicBool = AtomicBool.init(false), /// Points to the previous and next neighbors, based on the `text_offset`. /// This can be used to find, for example, the capacity of this `TextBlock`. @@ -38,7 +42,6 @@ extra: u32 = 0, pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 { return switch (self.getFile(macho_file)) { .dylib => unreachable, - .zig_object => |x| x.strtab.getAssumeExists(self.name), inline else => |x| x.getString(self.name), }; } @@ -98,6 +101,14 @@ pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void { } } +pub fn isAlive(self: Atom) bool { + return self.alive.load(.seq_cst); +} + +pub fn setAlive(self: *Atom, alive: bool) void { + _ = self.alive.swap(alive, .seq_cst); +} + pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk { const extra = self.getExtra(macho_file); return macho_file.getThunk(extra.thunk); @@ -350,7 +361,7 @@ pub fn allocate(self: *Atom, macho_file: *MachO) !void { _ = free_list.swapRemove(i); } - self.flags.alive = true; + self.setAlive(true); } pub fn shrink(self: *Atom, macho_file: *MachO) void { @@ -444,7 +455,7 @@ pub fn freeRelocs(self: *Atom, macho_file: *MachO) void { pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - assert(self.flags.alive); + assert(self.isAlive()); const relocs = self.getRelocs(macho_file); @@ -455,12 +466,12 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { .branch => { const symbol = rel.getTargetSymbol(self, macho_file); if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { - symbol.flags.stubs = true; + symbol.setSectionFlags(.{ .stubs = true }); if (symbol.flags.weak) { - macho_file.binds_to_weak = true; + macho_file.binds_to_weak.store(true, .seq_cst); } } else if (mem.startsWith(u8, symbol.getName(macho_file), "_objc_msgSend$")) { - symbol.flags.objc_stubs = true; + symbol.setSectionFlags(.{ .objc_stubs = true }); } }, @@ -474,19 +485,19 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { symbol.flags.interposable or macho_file.getTarget().cpu.arch == .aarch64) // TODO relax on arm64 { - symbol.flags.needs_got = true; + symbol.setSectionFlags(.{ .needs_got = true }); if (symbol.flags.weak) { - macho_file.binds_to_weak = true; + macho_file.binds_to_weak.store(true, .seq_cst); } } }, .zig_got_load => { - assert(rel.getTargetSymbol(self, macho_file).flags.has_zig_got); + assert(rel.getTargetSymbol(self, macho_file).getSectionFlags().has_zig_got); }, .got => { - rel.getTargetSymbol(self, macho_file).flags.needs_got = true; + rel.getTargetSymbol(self, macho_file).setSectionFlags(.{ .needs_got = true }); }, .tlv, @@ -502,9 +513,9 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { ); } if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { - symbol.flags.tlv_ptr = true; + symbol.setSectionFlags(.{ .tlv_ptr = true }); if (symbol.flags.weak) { - macho_file.binds_to_weak = true; + macho_file.binds_to_weak.store(true, .seq_cst); } } }, @@ -514,17 +525,17 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { if (rel.tag == .@"extern") { const symbol = rel.getTargetSymbol(self, macho_file); if (symbol.isTlvInit(macho_file)) { - macho_file.has_tlv = true; + macho_file.has_tlv.store(true, .seq_cst); continue; } if (symbol.flags.import) { if (symbol.flags.weak) { - macho_file.binds_to_weak = true; + macho_file.binds_to_weak.store(true, .seq_cst); } continue; } if (symbol.flags.@"export" and symbol.flags.weak) { - macho_file.binds_to_weak = true; + macho_file.binds_to_weak.store(true, .seq_cst); } } } @@ -548,6 +559,8 @@ fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool { const file = self.getFile(macho_file); const ref = file.getSymbolRef(rel.target, macho_file); if (ref.getFile(macho_file) == null) { + macho_file.undefs_mutex.lock(); + defer macho_file.undefs_mutex.unlock(); const gpa = macho_file.base.comp.gpa; const gop = try macho_file.undefs.getOrPut(gpa, file.getGlobals()[rel.target]); if (!gop.found_existing) { @@ -724,7 +737,7 @@ fn resolveRelocInner( assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(rel.meta.pcrel); - if (rel.getTargetSymbol(self, macho_file).flags.has_got) { + if (rel.getTargetSymbol(self, macho_file).getSectionFlags().has_got) { try writer.writeInt(i32, @intCast(G + A - P), .little); } else { try x86_64.relaxGotLoad(self, code[rel_offset - 3 ..], rel, macho_file); @@ -748,7 +761,7 @@ fn resolveRelocInner( assert(rel.meta.length == 2); assert(rel.meta.pcrel); const sym = rel.getTargetSymbol(self, macho_file); - if (sym.flags.tlv_ptr) { + if (sym.getSectionFlags().tlv_ptr) { const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); try writer.writeInt(i32, @intCast(S_ + A - P), .little); } else { @@ -776,7 +789,7 @@ fn resolveRelocInner( const target = switch (rel.type) { .page => S + A, .got_load_page => G + A, - .tlvp_page => if (sym.flags.tlv_ptr) blk: { + .tlvp_page => if (sym.getSectionFlags().tlv_ptr) blk: { const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); break :blk S_ + A; } else S + A, @@ -831,7 +844,7 @@ fn resolveRelocInner( const sym = rel.getTargetSymbol(self, macho_file); const target = target: { - const target = if (sym.flags.tlv_ptr) blk: { + const target = if (sym.getSectionFlags().tlv_ptr) blk: { const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); break :blk S_ + A; } else S + A; @@ -869,7 +882,7 @@ fn resolveRelocInner( } }; - var inst = if (sym.flags.tlv_ptr) aarch64.Instruction{ + var inst = if (sym.getSectionFlags().tlv_ptr) aarch64.Instruction{ .load_store_register = .{ .rt = reg_info.rd, .rn = reg_info.rn, @@ -906,15 +919,15 @@ const x86_64 = struct { encode(&.{inst}, code) catch return error.RelaxFail; }, else => |x| { - var err = try macho_file.addErrorWithNotes(2); - try err.addMsg(macho_file, "{s}: 0x{x}: 0x{x}: failed to relax relocation of type {}", .{ + var err = try macho_file.base.addErrorWithNotes(2); + try err.addMsg("{s}: 0x{x}: 0x{x}: failed to relax relocation of type {}", .{ self.getName(macho_file), self.getAddress(macho_file), rel.offset, rel.fmtPretty(.x86_64), }); - try err.addNote(macho_file, "expected .mov instruction but found .{s}", .{@tagName(x)}); - try err.addNote(macho_file, "while parsing {}", .{self.getFile(macho_file).fmtPath()}); + try err.addNote("expected .mov instruction but found .{s}", .{@tagName(x)}); + try err.addNote("while parsing {}", .{self.getFile(macho_file).fmtPath()}); return error.RelaxFailUnexpectedInstruction; }, } @@ -1142,7 +1155,7 @@ fn format2( atom.out_n_sect, atom.alignment, atom.size, atom.getRelocs(macho_file).len, atom.getExtra(macho_file).thunk, }); - if (!atom.flags.alive) try writer.writeAll(" : [*]"); + if (!atom.isAlive()) try writer.writeAll(" : [*]"); if (atom.getUnwindRecords(macho_file).len > 0) { try writer.writeAll(" : unwind{ "); const extra = atom.getExtra(macho_file); @@ -1158,14 +1171,6 @@ fn format2( pub const Index = u32; -pub const Flags = packed struct { - /// Specifies whether this atom is alive or has been garbage collected. - alive: bool = true, - - /// Specifies if this atom has been visited during garbage collection. - visited: bool = false, -}; - pub const Extra = struct { /// Index of the range extension thunk of this atom. thunk: u32 = 0, @@ -1209,6 +1214,7 @@ const trace = @import("../../tracy.zig").trace; const Allocator = mem.Allocator; const Atom = @This(); +const AtomicBool = std.atomic.Value(bool); const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 045bad712be38754ec1cd64b950e0a2c8900997a..0b9c12204f514ae64dc8588e4f082dfa82bdf9c2 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -7,6 +7,7 @@ const log = std.log.scoped(.link); const macho = std.macho; const mem = std.mem; const testing = std.testing; +const trace = @import("../../tracy.zig").trace; const Allocator = mem.Allocator; const Hasher = @import("hasher.zig").ParallelHasher; const MachO = @import("../MachO.zig"); @@ -264,6 +265,9 @@ pub fn writeAdhocSignature( opts: WriteOpts, writer: anytype, ) !void { + const tracy = trace(@src()); + defer tracy.end(); + const allocator = macho_file.base.comp.gpa; var header: macho.SuperBlob = .{ diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 9909279190839b991189e1f3f2348fe1f1230221..ce12397be45b9e5c0ce90acc15ce57eca2d28ed6 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -1,5 +1,9 @@ +/// Non-zero for fat dylibs +offset: u64, path: []const u8, index: File.Index, +file_handle: File.HandleIndex, +tag: enum { dylib, tbd }, exports: std.MultiArrayList(Export) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, @@ -11,7 +15,7 @@ symbols_extra: std.ArrayListUnmanaged(u32) = .{}, globals: std.ArrayListUnmanaged(MachO.SymbolResolver.Index) = .{}, dependents: std.ArrayListUnmanaged(Id) = .{}, rpaths: std.StringArrayHashMapUnmanaged(void) = .{}, -umbrella: File.Index = 0, +umbrella: File.Index, platform: ?MachO.Platform = null, needed: bool, @@ -23,16 +27,6 @@ referenced: bool = false, output_symtab_ctx: MachO.SymtabCtx = .{}, -pub fn isDylib(path: []const u8, fat_arch: ?fat.Arch) !bool { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - if (fat_arch) |arch| { - try file.seekTo(arch.offset); - } - const header = file.reader().readStruct(macho.mach_header_64) catch return false; - return header.filetype == macho.MH_DYLIB; -} - pub fn deinit(self: *Dylib, allocator: Allocator) void { allocator.free(self.path); self.exports.deinit(allocator); @@ -51,12 +45,21 @@ pub fn deinit(self: *Dylib, allocator: Allocator) void { self.rpaths.deinit(allocator); } -pub fn parse(self: *Dylib, macho_file: *MachO, file: std.fs.File, fat_arch: ?fat.Arch) !void { +pub fn parse(self: *Dylib, macho_file: *MachO) !void { + switch (self.tag) { + .tbd => try self.parseTbd(macho_file), + .dylib => try self.parseBinary(macho_file), + } + try self.initSymbols(macho_file); +} + +fn parseBinary(self: *Dylib, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = macho_file.base.comp.gpa; - const offset = if (fat_arch) |ar| ar.offset else 0; + const file = macho_file.getFileHandle(self.file_handle); + const offset = self.offset; log.debug("parsing dylib from binary: {s}", .{self.path}); @@ -258,13 +261,7 @@ fn parseTrie(self: *Dylib, data: []const u8, macho_file: *MachO) !void { try self.parseTrieNode(&it, gpa, arena.allocator(), ""); } -pub fn parseTbd( - self: *Dylib, - cpu_arch: std.Target.Cpu.Arch, - platform: MachO.Platform, - lib_stub: LibStub, - macho_file: *MachO, -) !void { +fn parseTbd(self: *Dylib, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -272,6 +269,12 @@ pub fn parseTbd( log.debug("parsing dylib from stub: {s}", .{self.path}); + const file = macho_file.getFileHandle(self.file_handle); + var lib_stub = LibStub.loadFromFile(gpa, file) catch |err| { + try macho_file.reportParseError2(self.index, "failed to parse TBD file: {s}", .{@errorName(err)}); + return error.MalformedTbd; + }; + defer lib_stub.deinit(); const umbrella_lib = lib_stub.inner[0]; { @@ -290,7 +293,8 @@ pub fn parseTbd( log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); - self.platform = platform; + const cpu_arch = macho_file.getTarget().cpu.arch; + self.platform = macho_file.platform; var matcher = try TargetMatcher.init(gpa, cpu_arch, self.platform.?.toApplePlatform()); defer matcher.deinit(); @@ -495,7 +499,7 @@ fn addObjCExport( try self.addExport(allocator, full_name, .{}); } -pub fn initSymbols(self: *Dylib, macho_file: *MachO) !void { +fn initSymbols(self: *Dylib, macho_file: *MachO) !void { const gpa = macho_file.base.comp.gpa; const nsyms = self.exports.items(.name).len; @@ -609,15 +613,18 @@ pub inline fn getUmbrella(self: Dylib, macho_file: *MachO) *Dylib { return macho_file.getFile(self.umbrella).?.dylib; } -fn addString(self: *Dylib, allocator: Allocator, name: []const u8) !u32 { +fn addString(self: *Dylib, allocator: Allocator, name: []const u8) !MachO.String { const off = @as(u32, @intCast(self.strtab.items.len)); - try self.strtab.writer(allocator).print("{s}\x00", .{name}); - return off; + try self.strtab.ensureUnusedCapacity(allocator, name.len + 1); + self.strtab.appendSliceAssumeCapacity(name); + self.strtab.appendAssumeCapacity(0); + return .{ .pos = off, .len = @intCast(name.len + 1) }; } -pub fn getString(self: Dylib, off: u32) [:0]const u8 { - assert(off < self.strtab.items.len); - return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0); +pub fn getString(self: Dylib, string: MachO.String) [:0]const u8 { + assert(string.pos < self.strtab.items.len and string.pos + string.len <= self.strtab.items.len); + if (string.len == 0) return ""; + return self.strtab.items[string.pos..][0 .. string.len - 1 :0]; } pub fn asFile(self: *Dylib) File { @@ -931,7 +938,7 @@ pub const Id = struct { }; const Export = struct { - name: u32, + name: MachO.String, flags: Flags, const Flags = packed struct { diff --git a/src/link/MachO/InternalObject.zig b/src/link/MachO/InternalObject.zig index ca6a53e9833eebdba45ce6696b29d4caedeff4e1..926e4a4a9fb6559a23e0438775bdd1105c6865d7 100644 --- a/src/link/MachO/InternalObject.zig +++ b/src/link/MachO/InternalObject.zig @@ -53,7 +53,7 @@ pub fn init(self: *InternalObject, allocator: Allocator) !void { pub fn initSymbols(self: *InternalObject, macho_file: *MachO) !void { const newSymbolAssumeCapacity = struct { - fn newSymbolAssumeCapacity(obj: *InternalObject, name: u32, args: struct { + fn newSymbolAssumeCapacity(obj: *InternalObject, name: MachO.String, args: struct { type: u8 = macho.N_UNDF | macho.N_EXT, desc: u16 = 0, }) Symbol.Index { @@ -69,7 +69,7 @@ pub fn initSymbols(self: *InternalObject, macho_file: *MachO) !void { const nlist_idx: u32 = @intCast(obj.symtab.items.len); const nlist = obj.symtab.addOneAssumeCapacity(); nlist.* = .{ - .n_strx = name, + .n_strx = name.pos, .n_type = args.type, .n_sect = 0, .n_desc = args.desc, @@ -197,16 +197,16 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void { try self.globals.ensureUnusedCapacity(gpa, nsyms); for (boundary_symbols.keys(), boundary_symbols.values()) |name, ref| { - const name_off = try self.addString(gpa, name); + const name_str = try self.addString(gpa, name); const sym_index = self.addSymbolAssumeCapacity(); self.boundary_symbols.appendAssumeCapacity(sym_index); const sym = &self.symbols.items[sym_index]; - sym.name = name_off; + sym.name = name_str; sym.visibility = .local; const nlist_idx: u32 = @intCast(self.symtab.items.len); const nlist = self.symtab.addOneAssumeCapacity(); nlist.* = .{ - .n_strx = name_off, + .n_strx = name_str.pos, .n_type = macho.N_SECT, .n_sect = 0, .n_desc = 0, @@ -273,7 +273,7 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil const nlist_idx: u32 = @intCast(self.symtab.items.len); const nlist = try self.symtab.addOne(gpa); nlist.* = .{ - .n_strx = name_str, + .n_strx = name_str.pos, .n_type = macho.N_SECT, .n_sect = @intCast(n_sect + 1), .n_desc = 0, @@ -373,15 +373,15 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi const name = MachO.eatPrefix(sym_name, "_objc_msgSend$").?; const selrefs_index = try self.addObjcMsgsendSections(name, macho_file); - const name_off = try self.addString(gpa, sym_name); + const name_str = try self.addString(gpa, sym_name); const sym_index = try self.addSymbol(gpa); const sym = &self.symbols.items[sym_index]; - sym.name = name_off; + sym.name = name_str; sym.visibility = .hidden; const nlist_idx: u32 = @intCast(self.symtab.items.len); const nlist = try self.symtab.addOne(gpa); nlist.* = .{ - .n_strx = name_off, + .n_strx = name_str.pos, .n_type = macho.N_SECT | macho.N_EXT | macho.N_PEXT, .n_sect = 0, .n_desc = 0, @@ -389,7 +389,7 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi }; sym.nlist_idx = nlist_idx; sym.extra = try self.addSymbolExtra(gpa, .{ .objc_selrefs = selrefs_index }); - sym.flags.objc_stubs = true; + sym.setSectionFlags(.{ .objc_stubs = true }); const idx = ref.getFile(macho_file).?.object.globals.items[ref.index]; try self.globals.append(gpa, idx); @@ -427,7 +427,7 @@ pub fn resolveLiterals(self: *InternalObject, lp: *MachO.LiteralPool, macho_file const lp_sym = lp.getSymbol(res.index, macho_file); const lp_atom = lp_sym.getAtom(macho_file).?; lp_atom.alignment = lp_atom.alignment.max(atom.alignment); - atom.flags.alive = false; + atom.setAlive(false); } atom.addExtra(.{ .literal_pool_index = res.index }, macho_file); } @@ -439,7 +439,7 @@ pub fn dedupLiterals(self: *InternalObject, lp: MachO.LiteralPool, macho_file: * for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const relocs = blk: { const extra = atom.getExtra(macho_file); @@ -464,7 +464,7 @@ pub fn dedupLiterals(self: *InternalObject, lp: MachO.LiteralPool, macho_file: * } for (self.symbols.items) |*sym| { - if (!sym.flags.objc_stubs) continue; + if (!sym.getSectionFlags().objc_stubs) continue; const extra = sym.getExtra(macho_file); const file = sym.getFile(macho_file).?; if (file.getIndex() != self.index) continue; @@ -490,20 +490,20 @@ pub fn scanRelocs(self: *InternalObject, macho_file: *MachO) void { if (self.getEntryRef(macho_file)) |ref| { if (ref.getFile(macho_file) != null) { const sym = ref.getSymbol(macho_file).?; - if (sym.flags.import) sym.flags.stubs = true; + if (sym.flags.import) sym.setSectionFlags(.{ .stubs = true }); } } if (self.getDyldStubBinderRef(macho_file)) |ref| { if (ref.getFile(macho_file) != null) { const sym = ref.getSymbol(macho_file).?; - sym.flags.needs_got = true; + sym.setSectionFlags(.{ .needs_got = true }); } } if (self.getObjcMsgSendRef(macho_file)) |ref| { if (ref.getFile(macho_file) != null) { const sym = ref.getSymbol(macho_file).?; // TODO is it always needed, or only if we are synthesising fast stubs - sym.flags.needs_got = true; + sym.setSectionFlags(.{ .needs_got = true }); } } } @@ -570,7 +570,7 @@ pub fn writeAtoms(self: *InternalObject, macho_file: *MachO) !void { for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; const off = std.math.cast(usize, atom.value) orelse return error.Overflow; @@ -624,17 +624,18 @@ fn getSectionData(self: *const InternalObject, index: u32) error{Overflow}![]con @panic("ref to non-existent section"); } -pub fn addString(self: *InternalObject, allocator: Allocator, name: []const u8) !u32 { +pub fn addString(self: *InternalObject, allocator: Allocator, string: []const u8) !MachO.String { const off: u32 = @intCast(self.strtab.items.len); - try self.strtab.ensureUnusedCapacity(allocator, name.len + 1); - self.strtab.appendSliceAssumeCapacity(name); + try self.strtab.ensureUnusedCapacity(allocator, string.len + 1); + self.strtab.appendSliceAssumeCapacity(string); self.strtab.appendAssumeCapacity(0); - return off; + return .{ .pos = off, .len = @intCast(string.len + 1) }; } -pub fn getString(self: InternalObject, off: u32) [:0]const u8 { - assert(off < self.strtab.items.len); - return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0); +pub fn getString(self: InternalObject, string: MachO.String) [:0]const u8 { + assert(string.pos < self.strtab.items.len and string.pos + string.len <= self.strtab.items.len); + if (string.len == 0) return ""; + return self.strtab.items[string.pos..][0 .. string.len - 1 :0]; } pub fn asFile(self: *InternalObject) File { diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 10d987b044370f5d84cffdea18278e578b249bae..38cb82e858ff384eaeed9948d76797985cddf56c 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -38,13 +38,6 @@ compact_unwind_ctx: CompactUnwindCtx = .{}, output_symtab_ctx: MachO.SymtabCtx = .{}, output_ar_state: Archive.ArState = .{}, -pub fn isObject(path: []const u8) !bool { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - const header = file.reader().readStruct(macho.mach_header_64) catch return false; - return header.filetype == macho.MH_OBJECT; -} - pub fn deinit(self: *Object, allocator: Allocator) void { if (self.in_archive) |*ar| allocator.free(ar.path); allocator.free(self.path); @@ -185,7 +178,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { fn rank(ctx: *const Object, nl: macho.nlist_64) u8 { if (!nl.ext()) { - const name = ctx.getString(nl.n_strx); + const name = ctx.getNStrx(nl.n_strx); if (name.len == 0) return 5; if (name[0] == 'l' or name[0] == 'L') return 4; return 3; @@ -270,9 +263,12 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { mem.eql(u8, isec.sectName(), "__compact_unwind") or isec.attrs() & macho.S_ATTR_DEBUG != 0) { - atom.flags.alive = false; + atom.setAlive(false); } } + + // Finally, we do a post-parse check for -ObjC to see if we need to force load this member anyhow. + self.alive = self.alive or (macho_file.force_load_objc and self.hasObjC()); } pub fn isCstringLiteral(sect: macho.section_64) bool { @@ -345,7 +341,7 @@ fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void { else sect.@"align"; const atom_index = try self.addAtom(allocator, .{ - .name = nlist.nlist.n_strx, + .name = .{ .pos = nlist.nlist.n_strx, .len = @intCast(self.getNStrx(nlist.nlist.n_strx).len + 1) }, .n_sect = @intCast(n_sect), .off = nlist.nlist.n_value - sect.addr, .size = size, @@ -469,7 +465,7 @@ fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, m const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator)); self.symtab.set(nlist_index, .{ .nlist = .{ - .n_strx = name_str, + .n_strx = name_str.pos, .n_type = macho.N_SECT, .n_sect = @intCast(atom.n_sect + 1), .n_desc = 0, @@ -536,7 +532,7 @@ fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator)); self.symtab.set(nlist_index, .{ .nlist = .{ - .n_strx = name_str, + .n_strx = name_str.pos, .n_type = macho.N_SECT, .n_sect = @intCast(atom.n_sect + 1), .n_desc = 0, @@ -594,7 +590,7 @@ fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator)); self.symtab.set(nlist_index, .{ .nlist = .{ - .n_strx = name_str, + .n_strx = name_str.pos, .n_type = macho.N_SECT, .n_sect = @intCast(atom.n_sect + 1), .n_desc = 0, @@ -649,7 +645,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO const lp_sym = lp.getSymbol(res.index, macho_file); const lp_atom = lp_sym.getAtom(macho_file).?; lp_atom.alignment = lp_atom.alignment.max(atom.alignment); - atom.flags.alive = false; + atom.setAlive(false); } atom.addExtra(.{ .literal_pool_index = res.index }, macho_file); } @@ -687,7 +683,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO const lp_sym = lp.getSymbol(res.index, macho_file); const lp_atom = lp_sym.getAtom(macho_file).?; lp_atom.alignment = lp_atom.alignment.max(atom.alignment); - atom.flags.alive = false; + atom.setAlive(false); } atom.addExtra(.{ .literal_pool_index = res.index }, macho_file); } @@ -701,7 +697,7 @@ pub fn dedupLiterals(self: *Object, lp: MachO.LiteralPool, macho_file: *MachO) v for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const relocs = blk: { const extra = atom.getExtra(macho_file); @@ -800,7 +796,7 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void { atom.* = atom_index; } else { try macho_file.reportParseError2(self.index, "symbol {s} not attached to any (sub)section", .{ - self.getString(nlist.n_strx), + self.getNStrx(nlist.n_strx), }); return error.MalformedObject; } @@ -825,7 +821,7 @@ fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void { const index = self.addSymbolAssumeCapacity(); const symbol = &self.symbols.items[index]; symbol.value = nlist.n_value; - symbol.name = nlist.n_strx; + symbol.name = .{ .pos = nlist.n_strx, .len = @intCast(self.getNStrx(nlist.n_strx).len + 1) }; symbol.nlist_idx = @intCast(i); symbol.extra = self.addSymbolExtraAssumeCapacity(.{}); @@ -898,7 +894,7 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f defer addr_lookup.deinit(); for (syms) |sym| { if (sym.sect() and (sym.ext() or sym.pext())) { - try addr_lookup.putNoClobber(self.getString(sym.n_strx), sym.n_value); + try addr_lookup.putNoClobber(self.getNStrx(sym.n_strx), sym.n_value); } } @@ -930,7 +926,7 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f }, macho.N_GSYM => { stab.is_func = false; - stab.index = sym_lookup.find(addr_lookup.get(self.getString(nlist.n_strx)).?); + stab.index = sym_lookup.find(addr_lookup.get(self.getNStrx(nlist.n_strx)).?); }, macho.N_STSYM => { stab.is_func = false; @@ -994,7 +990,7 @@ fn initRelocs(self: *Object, file: File.Handle, cpu_arch: std.Target.Cpu.Arch, m var next_reloc: u32 = 0; for (subsections.items) |subsection| { const atom = self.getAtom(subsection.atom).?; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; if (next_reloc >= relocs.items.len) break; const end_addr = atom.off + atom.size; const rel_index = next_reloc; @@ -1487,7 +1483,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void { if (!nlist.ext()) continue; if (nlist.sect()) { const atom = self.getAtom(atom_index).?; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; } const gop = try macho_file.resolver.getOrPut(gpa, .{ @@ -1556,7 +1552,7 @@ pub fn scanRelocs(self: *Object, macho_file: *MachO) !void { for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; try atom.scanRelocs(macho_file); @@ -1567,10 +1563,10 @@ pub fn scanRelocs(self: *Object, macho_file: *MachO) !void { if (!rec.alive) continue; if (rec.getFde(macho_file)) |fde| { if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| { - sym.flags.needs_got = true; + sym.setSectionFlags(.{ .needs_got = true }); } } else if (rec.getPersonality(macho_file)) |sym| { - sym.flags.needs_got = true; + sym.setSectionFlags(.{ .needs_got = true }); } } } @@ -1712,7 +1708,7 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *M const gpa = macho_file.base.comp.gpa; for (self.symtab.items(.nlist)) |nlist| { if (!nlist.ext() or (nlist.undf() and !nlist.tentative())) continue; - const off = try ar_symtab.strtab.insert(gpa, self.getString(nlist.n_strx)); + const off = try ar_symtab.strtab.insert(gpa, self.getNStrx(nlist.n_strx)); try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index }); } } @@ -1749,7 +1745,7 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void { const ref = self.getSymbolRef(@intCast(i), macho_file); const file = ref.getFile(macho_file) orelse continue; if (file.getIndex() != self.index) continue; - if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue; + if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue; if (sym.isSymbolStab(macho_file)) continue; const name = sym.getName(macho_file); if (name.len == 0) continue; @@ -1858,7 +1854,7 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void { } for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; const value = math.cast(usize, atom.value) orelse return error.Overflow; @@ -1897,7 +1893,7 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void { } for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; const value = math.cast(usize, atom.value) orelse return error.Overflow; @@ -2296,17 +2292,23 @@ pub fn getAtomRelocs(self: *const Object, atom: Atom, macho_file: *MachO) []cons return relocs.items[extra.rel_index..][0..extra.rel_count]; } -fn addString(self: *Object, allocator: Allocator, name: [:0]const u8) error{OutOfMemory}!u32 { +fn addString(self: *Object, allocator: Allocator, string: [:0]const u8) error{OutOfMemory}!MachO.String { const off: u32 = @intCast(self.strtab.items.len); - try self.strtab.ensureUnusedCapacity(allocator, name.len + 1); - self.strtab.appendSliceAssumeCapacity(name); + try self.strtab.ensureUnusedCapacity(allocator, string.len + 1); + self.strtab.appendSliceAssumeCapacity(string); self.strtab.appendAssumeCapacity(0); - return off; + return .{ .pos = off, .len = @intCast(string.len + 1) }; } -pub fn getString(self: Object, off: u32) [:0]const u8 { - assert(off < self.strtab.items.len); - return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0); +pub fn getString(self: Object, string: MachO.String) [:0]const u8 { + assert(string.pos < self.strtab.items.len and string.pos + string.len <= self.strtab.items.len); + if (string.len == 0) return ""; + return self.strtab.items[string.pos..][0 .. string.len - 1 :0]; +} + +fn getNStrx(self: Object, n_strx: u32) [:0]const u8 { + assert(n_strx < self.strtab.items.len); + return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + n_strx)), 0); } pub fn hasUnwindRecords(self: Object) bool { @@ -2325,9 +2327,9 @@ fn hasSymbolStabs(self: Object) bool { return self.stab_files.items.len > 0; } -pub fn hasObjc(self: Object) bool { +fn hasObjC(self: Object) bool { for (self.symtab.items(.nlist)) |nlist| { - const name = self.getString(nlist.n_strx); + const name = self.getNStrx(nlist.n_strx); if (mem.startsWith(u8, name, "_OBJC_CLASS_$_")) return true; } for (self.sections.items(.header)) |sect| { @@ -2350,7 +2352,7 @@ pub fn asFile(self: *Object) File { } const AddAtomArgs = struct { - name: u32, + name: MachO.String, n_sect: u8, off: u64, size: u64, @@ -2694,17 +2696,17 @@ const StabFile = struct { fn getCompDir(sf: StabFile, object: Object) [:0]const u8 { const nlist = object.symtab.items(.nlist)[sf.comp_dir]; - return object.getString(nlist.n_strx); + return object.getNStrx(nlist.n_strx); } fn getTuName(sf: StabFile, object: Object) [:0]const u8 { const nlist = object.symtab.items(.nlist)[sf.comp_dir + 1]; - return object.getString(nlist.n_strx); + return object.getNStrx(nlist.n_strx); } fn getOsoPath(sf: StabFile, object: Object) [:0]const u8 { const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2]; - return object.getString(nlist.n_strx); + return object.getNStrx(nlist.n_strx); } fn getOsoModTime(sf: StabFile, object: Object) u64 { @@ -2762,8 +2764,8 @@ const StabFile = struct { }; const CompileUnit = struct { - comp_dir: u32, - tu_name: u32, + comp_dir: MachO.String, + tu_name: MachO.String, fn getCompDir(cu: CompileUnit, object: Object) [:0]const u8 { return object.getString(cu.comp_dir); diff --git a/src/link/MachO/Symbol.zig b/src/link/MachO/Symbol.zig index 72cd55bf5a3c9ec9aaf1f28c78537a2a6cf4b704..6e5af75fc24665363bca1705a4b94059f5a8ae6f 100644 --- a/src/link/MachO/Symbol.zig +++ b/src/link/MachO/Symbol.zig @@ -4,7 +4,7 @@ value: u64 = 0, /// Offset into the linker's intern table. -name: u32 = 0, +name: MachO.String = .{}, /// File where this symbol is defined. file: File.Index = 0, @@ -23,6 +23,8 @@ nlist_idx: u32 = 0, /// Misc flags for the symbol packaged as packed struct for compression. flags: Flags = .{}, +sect_flags: std.atomic.Value(u8) = std.atomic.Value(u8).init(0), + visibility: Visibility = .local, extra: u32 = 0, @@ -55,7 +57,6 @@ pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool { pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 { return switch (symbol.getFile(macho_file).?) { - .zig_object => |x| x.strtab.getAssumeExists(symbol.name), inline else => |x| x.getString(symbol.name), }; } @@ -69,6 +70,14 @@ pub fn getOutputSectionIndex(symbol: Symbol, macho_file: *MachO) u8 { return symbol.out_n_sect; } +pub fn getSectionFlags(symbol: Symbol) SectionFlags { + return @bitCast(symbol.sect_flags.load(.seq_cst)); +} + +pub fn setSectionFlags(symbol: *Symbol, flags: SectionFlags) void { + _ = symbol.sect_flags.fetchOr(@bitCast(flags), .seq_cst); +} + pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File { return macho_file.getFile(symbol.file); } @@ -116,9 +125,9 @@ pub fn getAddress(symbol: Symbol, opts: struct { stubs: bool = true, }, macho_file: *MachO) u64 { if (opts.stubs) { - if (symbol.flags.stubs) { + if (symbol.getSectionFlags().stubs) { return symbol.getStubsAddress(macho_file); - } else if (symbol.flags.objc_stubs) { + } else if (symbol.getSectionFlags().objc_stubs) { return symbol.getObjcStubsAddress(macho_file); } } @@ -127,25 +136,25 @@ pub fn getAddress(symbol: Symbol, opts: struct { } pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 { - if (!symbol.flags.has_got) return 0; + if (!symbol.getSectionFlags().has_got) return 0; const extra = symbol.getExtra(macho_file); return macho_file.got.getAddress(extra.got, macho_file); } pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { - if (!symbol.flags.stubs) return 0; + if (!symbol.getSectionFlags().stubs) return 0; const extra = symbol.getExtra(macho_file); return macho_file.stubs.getAddress(extra.stubs, macho_file); } pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { - if (!symbol.flags.objc_stubs) return 0; + if (!symbol.getSectionFlags().objc_stubs) return 0; const extra = symbol.getExtra(macho_file); return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file); } pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 { - if (!symbol.flags.objc_stubs) return 0; + if (!symbol.getSectionFlags().objc_stubs) return 0; const extra = symbol.getExtra(macho_file); const file = symbol.getFile(macho_file).?; return switch (file) { @@ -155,7 +164,7 @@ pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 { } pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 { - if (!symbol.flags.tlv_ptr) return 0; + if (!symbol.getSectionFlags().tlv_ptr) return 0; const extra = symbol.getExtra(macho_file); return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file); } @@ -167,14 +176,14 @@ const GetOrCreateZigGotEntryResult = struct { pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult { assert(!macho_file.base.isRelocatable()); - assert(symbol.flags.needs_zig_got); - if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).zig_got }; + assert(symbol.getSectionFlags().needs_zig_got); + if (symbol.getSectionFlags().has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).zig_got }; const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file); return .{ .found_existing = false, .index = index }; } pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 { - if (!symbol.flags.has_zig_got) return 0; + if (!symbol.getSectionFlags().has_zig_got) return 0; const extras = symbol.getExtra(macho_file); return macho_file.zig_got.entryAddress(extras.zig_got, macho_file); } @@ -384,7 +393,9 @@ pub const Flags = packed struct { /// Whether the symbol makes into the output symtab or not. output_symtab: bool = false, +}; +pub const SectionFlags = packed struct(u8) { /// Whether the symbol contains __got indirection. needs_got: bool = false, has_got: bool = false, @@ -401,6 +412,8 @@ pub const Flags = packed struct { /// Whether the symbol contains __objc_stubs indirection. objc_stubs: bool = false, + + _: u1 = 0, }; pub const Visibility = enum { diff --git a/src/link/MachO/UnwindInfo.zig b/src/link/MachO/UnwindInfo.zig index 8fb4b2ce6301aa5fb0ae2f447f4a0e33a267dc2d..42172b85187327c997303355a5c2638a2e6ccfeb 100644 --- a/src/link/MachO/UnwindInfo.zig +++ b/src/link/MachO/UnwindInfo.zig @@ -53,7 +53,7 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void { for (macho_file.sections.items(.atoms)) |atoms| { for (atoms.items) |ref| { const atom = ref.getAtom(macho_file) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const recs = atom.getUnwindRecords(macho_file); const file = atom.getFile(macho_file); try info.records.ensureUnusedCapacity(gpa, recs.len); diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index f8ec5171f49847e4c3f8ae0b8c32892ec5ba5b5f..4e939008a590c1a4d5502b6b758203da856fafa3 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -141,7 +141,7 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void { } } -fn newSymbol(self: *ZigObject, allocator: Allocator, name: u32, args: struct { +fn newSymbol(self: *ZigObject, allocator: Allocator, name: MachO.String, args: struct { type: u8 = macho.N_UNDF | macho.N_EXT, desc: u16 = 0, }) !Symbol.Index { @@ -158,7 +158,7 @@ fn newSymbol(self: *ZigObject, allocator: Allocator, name: u32, args: struct { const nlist_idx: u32 = @intCast(self.symtab.addOneAssumeCapacity()); self.symtab.set(nlist_idx, .{ .nlist = .{ - .n_strx = name, + .n_strx = name.pos, .n_type = args.type, .n_sect = 0, .n_desc = args.desc, @@ -174,7 +174,7 @@ fn newSymbol(self: *ZigObject, allocator: Allocator, name: u32, args: struct { return index; } -fn newAtom(self: *ZigObject, allocator: Allocator, name: u32, macho_file: *MachO) !Atom.Index { +fn newAtom(self: *ZigObject, allocator: Allocator, name: MachO.String, macho_file: *MachO) !Atom.Index { try self.atoms.ensureUnusedCapacity(allocator, 1); try self.atoms_extra.ensureUnusedCapacity(allocator, @sizeOf(Atom.Extra)); try self.atoms_indexes.ensureUnusedCapacity(allocator, 1); @@ -192,7 +192,7 @@ fn newAtom(self: *ZigObject, allocator: Allocator, name: u32, macho_file: *MachO return index; } -fn newSymbolWithAtom(self: *ZigObject, allocator: Allocator, name: u32, macho_file: *MachO) !Symbol.Index { +fn newSymbolWithAtom(self: *ZigObject, allocator: Allocator, name: MachO.String, macho_file: *MachO) !Symbol.Index { const atom_index = try self.newAtom(allocator, name, macho_file); const sym_index = try self.newSymbol(allocator, name, .{ .type = macho.N_SECT }); const sym = &self.symbols.items[sym_index]; @@ -245,7 +245,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void { if (!nlist.ext()) continue; if (nlist.sect()) { const atom = self.getAtom(atom_index).?; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; } const gop = try macho_file.resolver.getOrPut(gpa, .{ @@ -391,7 +391,7 @@ pub fn claimUnresolved(self: *ZigObject, macho_file: *MachO) void { pub fn scanRelocs(self: *ZigObject, macho_file: *MachO) !void { for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; try atom.scanRelocs(macho_file); @@ -403,7 +403,7 @@ pub fn resolveRelocs(self: *ZigObject, macho_file: *MachO) !void { var has_error = false; for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = &macho_file.sections.items(.header)[atom.out_n_sect]; if (sect.isZerofill()) continue; if (!macho_file.isZigSection(atom.out_n_sect)) continue; // Non-Zig sections are handled separately @@ -450,7 +450,7 @@ pub fn resolveRelocs(self: *ZigObject, macho_file: *MachO) !void { pub fn calcNumRelocs(self: *ZigObject, macho_file: *MachO) void { for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const header = &macho_file.sections.items(.header)[atom.out_n_sect]; if (header.isZerofill()) continue; if (!macho_file.isZigSection(atom.out_n_sect) and !macho_file.isDebugSection(atom.out_n_sect)) continue; @@ -465,7 +465,7 @@ pub fn writeRelocs(self: *ZigObject, macho_file: *MachO) !void { for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const header = macho_file.sections.items(.header)[atom.out_n_sect]; const relocs = macho_file.sections.items(.relocs)[atom.out_n_sect].items; if (header.isZerofill()) continue; @@ -505,7 +505,7 @@ pub fn writeAtomsRelocatable(self: *ZigObject, macho_file: *MachO) !void { for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; if (macho_file.isZigSection(atom.out_n_sect)) continue; @@ -529,7 +529,7 @@ pub fn writeAtoms(self: *ZigObject, macho_file: *MachO) !void { for (self.getAtoms()) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; if (macho_file.isZigSection(atom.out_n_sect)) continue; @@ -549,7 +549,7 @@ pub fn calcSymtabSize(self: *ZigObject, macho_file: *MachO) void { const ref = self.getSymbolRef(@intCast(i), macho_file); const file = ref.getFile(macho_file) orelse continue; if (file.getIndex() != self.index) continue; - if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue; + if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue; sym.flags.output_symtab = true; if (sym.isLocal()) { sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file); @@ -914,7 +914,7 @@ pub fn updateDecl( const lib_name = variable.lib_name.toSlice(&mod.intern_pool); const index = try self.getGlobalSymbol(macho_file, name, lib_name); const sym = &self.symbols.items[index]; - sym.flags.needs_got = true; + sym.setSectionFlags(.{ .needs_got = true }); return; } @@ -992,10 +992,10 @@ fn updateDeclCode( const sym_name = try std.fmt.allocPrintZ(gpa, "_{s}", .{decl.fqn.toSlice(ip)}); defer gpa.free(sym_name); - sym.name = try self.strtab.insert(gpa, sym_name); - atom.flags.alive = true; + sym.name = try self.addString(gpa, sym_name); + atom.setAlive(true); atom.name = sym.name; - nlist.n_strx = sym.name; + nlist.n_strx = sym.name.pos; nlist.n_type = macho.N_SECT; nlist.n_sect = sect_index + 1; self.symtab.items(.size)[sym.nlist_idx] = code.len; @@ -1018,7 +1018,7 @@ fn updateDeclCode( if (!macho_file.base.isRelocatable()) { log.debug(" (updating offset table entry)", .{}); - assert(sym.flags.has_zig_got); + assert(sym.getSectionFlags().has_zig_got); const extra = sym.getExtra(macho_file); try macho_file.zig_got.writeOne(macho_file, extra.zig_got); } @@ -1034,7 +1034,7 @@ fn updateDeclCode( errdefer self.freeDeclMetadata(macho_file, sym_index); sym.value = 0; - sym.flags.needs_zig_got = true; + sym.setSectionFlags(.{ .needs_zig_got = true }); nlist.n_value = 0; if (!macho_file.base.isRelocatable()) { @@ -1090,15 +1090,15 @@ fn createTlvInitializer( const gpa = macho_file.base.comp.gpa; const sym_name = try std.fmt.allocPrint(gpa, "{s}$tlv$init", .{name}); defer gpa.free(sym_name); - const off = try self.strtab.insert(gpa, sym_name); + const string = try self.addString(gpa, sym_name); - const sym_index = try self.newSymbolWithAtom(gpa, off, macho_file); + const sym_index = try self.newSymbolWithAtom(gpa, string, macho_file); const sym = &self.symbols.items[sym_index]; const nlist = &self.symtab.items(.nlist)[sym.nlist_idx]; const atom = sym.getAtom(macho_file).?; sym.out_n_sect = sect_index; atom.out_n_sect = sect_index; - atom.flags.alive = true; + atom.setAlive(true); atom.alignment = alignment; atom.size = code.len; nlist.n_sect = sect_index + 1; @@ -1142,10 +1142,10 @@ fn createTlvDescriptor( atom.out_n_sect = sect_index; sym.value = 0; - sym.name = try self.strtab.insert(gpa, name); - atom.flags.alive = true; + sym.name = try self.addString(gpa, name); + atom.setAlive(true); atom.name = sym.name; - nlist.n_strx = sym.name; + nlist.n_strx = sym.name.pos; nlist.n_sect = sect_index + 1; nlist.n_type = macho.N_SECT; nlist.n_value = 0; @@ -1296,8 +1296,8 @@ fn lowerConst( var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); - const name_str_index = try self.strtab.insert(gpa, name); - const sym_index = try self.newSymbolWithAtom(gpa, name_str_index, macho_file); + const name_str = try self.addString(gpa, name); + const sym_index = try self.newSymbolWithAtom(gpa, name_str, macho_file); const res = try codegen.generateSymbol(&macho_file.base, pt, src_loc, val, &code_buffer, .{ .none = {}, @@ -1317,7 +1317,7 @@ fn lowerConst( self.symtab.items(.size)[sym.nlist_idx] = code.len; const atom = sym.getAtom(macho_file).?; - atom.flags.alive = true; + atom.setAlive(true); atom.alignment = required_alignment; atom.size = code.len; atom.out_n_sect = output_section_index; @@ -1447,13 +1447,13 @@ fn updateLazySymbol( var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); - const name_str_index = blk: { + const name_str = blk: { const name = try std.fmt.allocPrint(gpa, "__lazy_{s}_{}", .{ @tagName(lazy_sym.kind), lazy_sym.ty.fmt(pt), }); defer gpa.free(name); - break :blk try self.strtab.insert(gpa, name); + break :blk try self.addString(gpa, name); }; const src = lazy_sym.ty.srcLocOrNull(mod) orelse Module.LazySrcLoc.unneeded; @@ -1480,18 +1480,18 @@ fn updateLazySymbol( .const_data => macho_file.zig_const_sect_index.?, }; const sym = &self.symbols.items[symbol_index]; - sym.name = name_str_index; + sym.name = name_str; sym.out_n_sect = output_section_index; const nlist = &self.symtab.items(.nlist)[sym.nlist_idx]; - nlist.n_strx = name_str_index; + nlist.n_strx = name_str.pos; nlist.n_type = macho.N_SECT; nlist.n_sect = output_section_index + 1; self.symtab.items(.size)[sym.nlist_idx] = code.len; const atom = sym.getAtom(macho_file).?; - atom.flags.alive = true; - atom.name = name_str_index; + atom.setAlive(true); + atom.name = name_str; atom.alignment = required_alignment; atom.size = code.len; atom.out_n_sect = output_section_index; @@ -1500,7 +1500,7 @@ fn updateLazySymbol( errdefer self.freeDeclMetadata(macho_file, symbol_index); sym.value = 0; - sym.flags.needs_zig_got = true; + sym.setSectionFlags(.{ .needs_zig_got = true }); nlist.n_value = 0; if (!macho_file.base.isRelocatable()) { @@ -1553,10 +1553,10 @@ pub fn getGlobalSymbol(self: *ZigObject, macho_file: *MachO, name: []const u8, l const gpa = macho_file.base.comp.gpa; const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name}); defer gpa.free(sym_name); - const off = try self.strtab.insert(gpa, sym_name); - const lookup_gop = try self.globals_lookup.getOrPut(gpa, off); + const name_str = try self.addString(gpa, sym_name); + const lookup_gop = try self.globals_lookup.getOrPut(gpa, name_str.pos); if (!lookup_gop.found_existing) { - const sym_index = try self.newSymbol(gpa, off, .{}); + const sym_index = try self.newSymbol(gpa, name_str, .{}); const sym = &self.symbols.items[sym_index]; lookup_gop.value_ptr.* = sym.nlist_idx; } @@ -1571,12 +1571,12 @@ pub fn getOrCreateMetadataForDecl( const gpa = macho_file.base.comp.gpa; const gop = try self.decls.getOrPut(gpa, decl_index); if (!gop.found_existing) { - const sym_index = try self.newSymbolWithAtom(gpa, 0, macho_file); + const sym_index = try self.newSymbolWithAtom(gpa, .{}, macho_file); const sym = &self.symbols.items[sym_index]; if (isThreadlocal(macho_file, decl_index)) { sym.flags.tlv = true; } else { - sym.flags.needs_zig_got = true; + sym.setSectionFlags(.{ .needs_zig_got = true }); } gop.value_ptr.* = .{ .symbol_index = sym_index }; } @@ -1609,9 +1609,9 @@ pub fn getOrCreateMetadataForLazySymbol( }; switch (metadata.state.*) { .unused => { - const symbol_index = try self.newSymbolWithAtom(gpa, 0, macho_file); + const symbol_index = try self.newSymbolWithAtom(gpa, .{}, macho_file); const sym = &self.symbols.items[symbol_index]; - sym.flags.needs_zig_got = true; + sym.setSectionFlags(.{ .needs_zig_got = true }); metadata.symbol_index.* = symbol_index; }, .pending_flush => return metadata.symbol_index.*, @@ -1762,6 +1762,16 @@ pub fn setSymbolExtra(self: *ZigObject, index: u32, extra: Symbol.Extra) void { } } +fn addString(self: *ZigObject, allocator: Allocator, string: []const u8) !MachO.String { + const off = try self.strtab.insert(allocator, string); + return .{ .pos = off, .len = @intCast(string.len + 1) }; +} + +pub fn getString(self: ZigObject, string: MachO.String) [:0]const u8 { + if (string.len == 0) return ""; + return self.strtab.buffer.items[string.pos..][0 .. string.len - 1 :0]; +} + pub fn asFile(self: *ZigObject) File { return .{ .zig_object = self }; } diff --git a/src/link/MachO/dead_strip.zig b/src/link/MachO/dead_strip.zig index eb27109a82131b14dc50ba540a099d6df9889b49..30f53d3744f75b82a85deadcf4c974ba9f210676 100644 --- a/src/link/MachO/dead_strip.zig +++ b/src/link/MachO/dead_strip.zig @@ -82,9 +82,8 @@ fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !v } fn markAtom(atom: *Atom) bool { - const already_visited = atom.flags.visited; - atom.flags.visited = true; - return atom.flags.alive and !already_visited; + const already_visited = atom.visited.swap(true, .seq_cst); + return atom.isAlive() and !already_visited; } fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void { @@ -105,7 +104,7 @@ fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void { !(mem.eql(u8, isec.sectName(), "__eh_frame") or mem.eql(u8, isec.sectName(), "__compact_unwind") or isec.attrs() & macho.S_ATTR_DEBUG != 0) and - !atom.flags.alive and refersLive(atom, macho_file)) + !atom.isAlive() and refersLive(atom, macho_file)) { markLive(atom, macho_file); loop = true; @@ -116,8 +115,8 @@ fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void { } fn markLive(atom: *Atom, macho_file: *MachO) void { - assert(atom.flags.visited); - atom.flags.alive = true; + assert(atom.visited.load(.seq_cst)); + atom.setAlive(true); track_live_log.debug("{}marking live atom({d},{s})", .{ track_live_level, atom.atom_index, @@ -170,7 +169,7 @@ fn refersLive(atom: *Atom, macho_file: *MachO) bool { }, }; if (target_atom) |ta| { - if (ta.flags.alive) return true; + if (ta.isAlive()) return true; } } return false; @@ -181,9 +180,10 @@ fn prune(objects: []const File.Index, macho_file: *MachO) void { const file = macho_file.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (atom.flags.alive and !atom.flags.visited) { - atom.flags.alive = false; - atom.markUnwindRecordsDead(macho_file); + if (!atom.visited.load(.seq_cst)) { + if (atom.alive.cmpxchgStrong(true, false, .seq_cst, .seq_cst) == null) { + atom.markUnwindRecordsDead(macho_file); + } } } } diff --git a/src/link/MachO/dyld_info/Rebase.zig b/src/link/MachO/dyld_info/Rebase.zig index c0bcb42ed17d1f7f5a7e9285d3439c538b38e280..8809f130ff61d739d2165b06f79eded87cf633dd 100644 --- a/src/link/MachO/dyld_info/Rebase.zig +++ b/src/link/MachO/dyld_info/Rebase.zig @@ -35,7 +35,7 @@ pub fn updateSize(rebase: *Rebase, macho_file: *MachO) !void { const file = macho_file.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; if (atom.getInputSection(macho_file).isZerofill()) continue; const atom_addr = atom.getAddress(macho_file); const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect]; diff --git a/src/link/MachO/dyld_info/Trie.zig b/src/link/MachO/dyld_info/Trie.zig index 44e6cd4234372046292db96673b49fe804b40503..aed7b61df8727ed744b11842f0ea8db44f7f4e22 100644 --- a/src/link/MachO/dyld_info/Trie.zig +++ b/src/link/MachO/dyld_info/Trie.zig @@ -102,7 +102,7 @@ pub fn updateSize(self: *Trie, macho_file: *MachO) !void { if (ref.getFile(macho_file) == null) continue; const sym = ref.getSymbol(macho_file).?; if (!sym.flags.@"export") continue; - if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue; + if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue; var flags: u64 = if (sym.flags.abs) macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE else if (sym.flags.tlv) @@ -111,8 +111,8 @@ pub fn updateSize(self: *Trie, macho_file: *MachO) !void { macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR; if (sym.flags.weak) { flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION; - macho_file.weak_defines = true; - macho_file.binds_to_weak = true; + macho_file.weak_defines.store(true, .seq_cst); + macho_file.binds_to_weak.store(true, .seq_cst); } try self.put(gpa, .{ .name = sym.getName(macho_file), diff --git a/src/link/MachO/dyld_info/bind.zig b/src/link/MachO/dyld_info/bind.zig index 94e7c7ef29932aa20468ea32b9aa964ed7b37b4a..310118af4138ca4b46721dbada8cf8c1c71cb5eb 100644 --- a/src/link/MachO/dyld_info/bind.zig +++ b/src/link/MachO/dyld_info/bind.zig @@ -10,10 +10,7 @@ pub const Entry = struct { if (entry.target.eql(other.target)) { return entry.offset < other.offset; } - if (entry.target.file == other.target.file) { - return entry.target.index < other.target.index; - } - return entry.target.file < other.target.file; + return entry.target.lessThan(other.target); } return entry.segment_id < other.segment_id; } @@ -47,7 +44,7 @@ pub const Bind = struct { const file = macho_file.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; if (atom.getInputSection(macho_file).isZerofill()) continue; const atom_addr = atom.getAddress(macho_file); const relocs = atom.getRelocs(macho_file); @@ -299,7 +296,7 @@ pub const WeakBind = struct { const file = macho_file.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; if (atom.getInputSection(macho_file).isZerofill()) continue; const atom_addr = atom.getAddress(macho_file); const relocs = atom.getRelocs(macho_file); diff --git a/src/link/MachO/fat.zig b/src/link/MachO/fat.zig index 5542d70dc06d3a9e2a8fff464d1c536de2bbd4a8..7772f7a4de89a51983e8c71b27051739c5fc59e9 100644 --- a/src/link/MachO/fat.zig +++ b/src/link/MachO/fat.zig @@ -8,11 +8,17 @@ const native_endian = builtin.target.cpu.arch.endian(); const MachO = @import("../MachO.zig"); -pub fn isFatLibrary(path: []const u8) !bool { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - const hdr = file.reader().readStructEndian(macho.fat_header, .big) catch return false; - return hdr.magic == macho.FAT_MAGIC; +pub fn readFatHeader(file: std.fs.File) !macho.fat_header { + return readFatHeaderGeneric(macho.fat_header, file, 0); +} + +fn readFatHeaderGeneric(comptime Hdr: type, file: std.fs.File, offset: usize) !Hdr { + var buffer: [@sizeOf(Hdr)]u8 = undefined; + const nread = try file.preadAll(&buffer, offset); + if (nread != buffer.len) return error.InputOutput; + var hdr = @as(*align(1) const Hdr, @ptrCast(&buffer)).*; + mem.byteSwapAllFields(Hdr, &hdr); + return hdr; } pub const Arch = struct { @@ -21,17 +27,12 @@ pub const Arch = struct { size: u32, }; -pub fn parseArchs(path: []const u8, buffer: *[2]Arch) ![]const Arch { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - const reader = file.reader(); - const fat_header = try reader.readStructEndian(macho.fat_header, .big); - assert(fat_header.magic == macho.FAT_MAGIC); - +pub fn parseArchs(file: std.fs.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch { var count: usize = 0; var fat_arch_index: u32 = 0; - while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { - const fat_arch = try reader.readStructEndian(macho.fat_arch, .big); + while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) { + const offset = @sizeOf(macho.fat_header) + @sizeOf(macho.fat_arch) * fat_arch_index; + const fat_arch = try readFatHeaderGeneric(macho.fat_arch, file, offset); // If we come across an architecture that we do not know how to handle, that's // fine because we can keep looking for one that might match. const arch: std.Target.Cpu.Arch = switch (fat_arch.cputype) { @@ -39,9 +40,9 @@ pub fn parseArchs(path: []const u8, buffer: *[2]Arch) ![]const Arch { macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, else => continue, }; - buffer[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size }; + out[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size }; count += 1; } - return buffer[0..count]; + return out[0..count]; } diff --git a/src/link/MachO/file.zig b/src/link/MachO/file.zig index b9d9d407c950d05631ef32e251e48cf1af4811e3..26b5957058f77040f179ac0a8be22a1f4237e5e6 100644 --- a/src/link/MachO/file.zig +++ b/src/link/MachO/file.zig @@ -37,11 +37,10 @@ pub const File = union(enum) { } pub fn scanRelocs(file: File, macho_file: *MachO) !void { - switch (file) { + return switch (file) { .dylib => unreachable, - .internal => |x| x.scanRelocs(macho_file), inline else => |x| x.scanRelocs(macho_file), - } + }; } /// Encodes symbol rank so that the following ordering applies: @@ -182,19 +181,19 @@ pub const File = union(enum) { if (ref.getFile(macho_file) == null) continue; if (ref.file != file.getIndex()) continue; const sym = ref.getSymbol(macho_file).?; - if (sym.flags.needs_got) { + if (sym.getSectionFlags().needs_got) { log.debug("'{s}' needs GOT", .{sym.getName(macho_file)}); try macho_file.got.addSymbol(ref, macho_file); } - if (sym.flags.stubs) { + if (sym.getSectionFlags().stubs) { log.debug("'{s}' needs STUBS", .{sym.getName(macho_file)}); try macho_file.stubs.addSymbol(ref, macho_file); } - if (sym.flags.tlv_ptr) { + if (sym.getSectionFlags().tlv_ptr) { log.debug("'{s}' needs TLV pointer", .{sym.getName(macho_file)}); try macho_file.tlv_ptr.addSymbol(ref, macho_file); } - if (sym.flags.objc_stubs) { + if (sym.getSectionFlags().objc_stubs) { log.debug("'{s}' needs OBJC STUBS", .{sym.getName(macho_file)}); try macho_file.objc_stubs.addSymbol(ref, macho_file); } @@ -268,6 +267,9 @@ pub const File = union(enum) { const ref_file = ref.getFile(macho_file) orelse continue; if (ref_file.getIndex() == file.getIndex()) continue; + macho_file.dupes_mutex.lock(); + defer macho_file.dupes_mutex.unlock(); + const gop = try macho_file.dupes.getOrPut(gpa, file.getGlobals()[i]); if (!gop.found_existing) { gop.value_ptr.* = .{}; @@ -281,7 +283,7 @@ pub const File = union(enum) { defer tracy.end(); for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); } } @@ -295,11 +297,18 @@ pub const File = union(enum) { pub fn writeAtoms(file: File, macho_file: *MachO) !void { return switch (file) { - .dylib, .zig_object => unreachable, + .dylib => unreachable, inline else => |x| x.writeAtoms(macho_file), }; } + pub fn writeAtomsRelocatable(file: File, macho_file: *MachO) !void { + return switch (file) { + .dylib, .internal => unreachable, + inline else => |x| x.writeAtomsRelocatable(macho_file), + }; + } + pub fn calcSymtabSize(file: File, macho_file: *MachO) void { return switch (file) { inline else => |x| x.calcSymtabSize(macho_file), @@ -335,6 +344,21 @@ pub const File = union(enum) { }; } + pub fn parse(file: File, macho_file: *MachO) !void { + return switch (file) { + .internal, .zig_object => unreachable, + .object => |x| x.parse(macho_file), + .dylib => |x| x.parse(macho_file), + }; + } + + pub fn parseAr(file: File, macho_file: *MachO) !void { + return switch (file) { + .internal, .zig_object, .dylib => unreachable, + .object => |x| x.parseAr(macho_file), + }; + } + pub const Index = u32; pub const Entry = union(enum) { diff --git a/src/link/MachO/hasher.zig b/src/link/MachO/hasher.zig index d8496ab706742f1841861261187b3f25fee4db5b..f10a2fe8cfe7508e38426cc2330395a74936ea83 100644 --- a/src/link/MachO/hasher.zig +++ b/src/link/MachO/hasher.zig @@ -55,6 +55,8 @@ pub fn ParallelHasher(comptime Hasher: type) type { out: *[hash_size]u8, err: *fs.File.PReadError!usize, ) void { + const tracy = trace(@src()); + defer tracy.end(); err.* = file.preadAll(buffer, fstart); Hasher.hash(buffer, out, .{}); } diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 51689ea226998878b35b38a207c16be9c57967e9..1ee40921ced13e52c1dadd5e38108bdb763f6d70 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -27,22 +27,21 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c } for (positionals.items) |obj| { - macho_file.parsePositional(obj.path, obj.must_link) catch |err| switch (err) { - error.MalformedObject, - error.MalformedArchive, - error.InvalidCpuArch, - error.InvalidTarget, - => continue, // already reported - error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an object file", .{}), + macho_file.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err| switch (err) { + error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an input file", .{}), else => |e| try macho_file.reportParseError( obj.path, - "unexpected error: parsing input file failed with error {s}", + "unexpected error: reading input file failed with error {s}", .{@errorName(e)}, ), }; } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (macho_file.base.hasErrors()) return error.FlushFailure; + + try macho_file.parseInputFiles(); + + if (macho_file.base.hasErrors()) return error.FlushFailure; try macho_file.resolveSymbols(); try macho_file.dedupLiterals(); @@ -93,22 +92,21 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? } for (positionals.items) |obj| { - parsePositional(macho_file, obj.path) catch |err| switch (err) { - error.MalformedObject, - error.MalformedArchive, - error.InvalidCpuArch, - error.InvalidTarget, - => continue, // already reported - error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an object file", .{}), + macho_file.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err| switch (err) { + error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an input file", .{}), else => |e| try macho_file.reportParseError( obj.path, - "unexpected error: parsing input file failed with error {s}", + "unexpected error: reading input file failed with error {s}", .{@errorName(e)}, ), }; } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (macho_file.base.hasErrors()) return error.FlushFailure; + + try parseInputFilesAr(macho_file); + + if (macho_file.base.hasErrors()) return error.FlushFailure; // First, we flush relocatable object file generated with our backends. if (macho_file.getZigObject()) |zo| { @@ -225,79 +223,19 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? try macho_file.base.file.?.setEndPos(total_size); try macho_file.base.file.?.pwriteAll(buffer.items, 0); - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (macho_file.base.hasErrors()) return error.FlushFailure; } -fn parsePositional(macho_file: *MachO, path: []const u8) MachO.ParseError!void { +fn parseInputFilesAr(macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - if (try Object.isObject(path)) { - try parseObject(macho_file, path); - } else if (try fat.isFatLibrary(path)) { - const fat_arch = try macho_file.parseFatLibrary(path); - if (try Archive.isArchive(path, fat_arch)) { - try parseArchive(macho_file, path, fat_arch); - } else return error.UnknownFileType; - } else if (try Archive.isArchive(path, null)) { - try parseArchive(macho_file, path, null); - } else return error.UnknownFileType; -} - -fn parseObject(macho_file: *MachO, path: []const u8) MachO.ParseError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = macho_file.base.comp.gpa; - const file = try std.fs.cwd().openFile(path, .{}); - errdefer file.close(); - const handle = try macho_file.addFileHandle(file); - const mtime: u64 = mtime: { - const stat = file.stat() catch break :mtime 0; - break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); - }; - const index = @as(File.Index, @intCast(try macho_file.files.addOne(gpa))); - macho_file.files.set(index, .{ - .object = .{ - .offset = 0, // TODO FAT objects - .path = try gpa.dupe(u8, path), - .file_handle = handle, - .mtime = mtime, - .index = index, - }, - }); - try macho_file.objects.append(gpa, index); - - const object = macho_file.getFile(index).?.object; - try object.parseAr(macho_file); -} - -fn parseArchive(macho_file: *MachO, path: []const u8, fat_arch: ?fat.Arch) MachO.ParseError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = macho_file.base.comp.gpa; - - const file = try std.fs.cwd().openFile(path, .{}); - errdefer file.close(); - const handle = try macho_file.addFileHandle(file); - - var archive = Archive{}; - defer archive.deinit(gpa); - try archive.parse(macho_file, path, handle, fat_arch); - var has_parse_error = false; - for (archive.objects.items) |extracted| { - const index = @as(File.Index, @intCast(try macho_file.files.addOne(gpa))); - macho_file.files.set(index, .{ .object = extracted }); - const object = &macho_file.files.items(.data)[index].object; - object.index = index; - object.parseAr(macho_file) catch |err| switch (err) { - error.InvalidCpuArch => has_parse_error = true, - else => |e| return e, + for (macho_file.objects.items) |index| { + macho_file.getFile(index).?.parseAr(macho_file) catch |err| switch (err) { + error.InvalidCpuArch => {}, // already reported + else => |e| try macho_file.reportParseError2(index, "unexpected error: parsing input file failed with error {s}", .{@errorName(e)}), }; - try macho_file.objects.append(gpa, index); } - if (has_parse_error) return error.MalformedArchive; } fn markExports(macho_file: *MachO) void { @@ -323,7 +261,7 @@ fn initOutputSections(macho_file: *MachO) !void { const file = macho_file.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.isAlive()) continue; atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); } } @@ -350,37 +288,54 @@ fn calcSectionSizes(macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - for (macho_file.sections.items(.atoms), 0..) |atoms, i| { - if (atoms.items.len == 0) continue; - calcSectionSize(macho_file, @intCast(i)); - } - if (macho_file.getZigObject()) |zo| { - // TODO this will create a race + // TODO this will create a race as we need to track merging of debug sections which we currently don't zo.calcNumRelocs(macho_file); - zo.calcSymtabSize(macho_file); } - if (macho_file.eh_frame_sect_index) |_| { - try calcEhFrameSize(macho_file); - } + const tp = macho_file.base.comp.thread_pool; + var wg: WaitGroup = .{}; + { + wg.reset(); + defer wg.wait(); + + for (macho_file.sections.items(.atoms), 0..) |atoms, i| { + if (atoms.items.len == 0) continue; + tp.spawnWg(&wg, calcSectionSizeWorker, .{ macho_file, @as(u8, @intCast(i)) }); + } + + if (macho_file.eh_frame_sect_index) |_| { + tp.spawnWg(&wg, calcEhFrameSizeWorker, .{macho_file}); + } - for (macho_file.objects.items) |index| { if (macho_file.unwind_info_sect_index) |_| { - macho_file.getFile(index).?.object.calcCompactUnwindSizeRelocatable(macho_file); + for (macho_file.objects.items) |index| { + tp.spawnWg(&wg, Object.calcCompactUnwindSizeRelocatable, .{ + macho_file.getFile(index).?.object, + macho_file, + }); + } } - macho_file.getFile(index).?.calcSymtabSize(macho_file); + + for (macho_file.objects.items) |index| { + tp.spawnWg(&wg, File.calcSymtabSize, .{ macho_file.getFile(index).?, macho_file }); + } + if (macho_file.getZigObject()) |zo| { + tp.spawnWg(&wg, File.calcSymtabSize, .{ zo.asFile(), macho_file }); + } + + tp.spawnWg(&wg, MachO.updateLinkeditSizeWorker, .{ macho_file, .data_in_code }); } - try macho_file.data_in_code.updateSize(macho_file); - if (macho_file.unwind_info_sect_index) |_| { calcCompactUnwindSize(macho_file); } try calcSymtabSize(macho_file); + + if (macho_file.has_errors.swap(false, .seq_cst)) return error.FlushFailure; } -fn calcSectionSize(macho_file: *MachO, sect_id: u8) void { +fn calcSectionSizeWorker(macho_file: *MachO, sect_id: u8) void { const tracy = trace(@src()); defer tracy.end(); @@ -401,14 +356,25 @@ fn calcSectionSize(macho_file: *MachO, sect_id: u8) void { } } -fn calcEhFrameSize(macho_file: *MachO) !void { +fn calcEhFrameSizeWorker(macho_file: *MachO) void { const tracy = trace(@src()); defer tracy.end(); + const doWork = struct { + fn doWork(mfile: *MachO, header: *macho.section_64) !void { + header.size = try eh_frame.calcSize(mfile); + header.@"align" = 3; + header.nreloc = eh_frame.calcNumRelocs(mfile); + } + }.doWork; + const header = &macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; - header.size = try eh_frame.calcSize(macho_file); - header.@"align" = 3; - header.nreloc = eh_frame.calcNumRelocs(macho_file); + doWork(macho_file, header) catch |err| { + macho_file.reportUnexpectedError("failed to calculate size of section '__TEXT,__eh_frame': {s}", .{ + @errorName(err), + }) catch {}; + _ = macho_file.has_errors.swap(true, .seq_cst); + }; } fn calcCompactUnwindSize(macho_file: *MachO) void { @@ -639,33 +605,74 @@ fn writeSections(macho_file: *MachO) !void { try macho_file.strtab.resize(gpa, cmd.strsize); macho_file.strtab.items[0] = 0; - for (macho_file.objects.items) |index| { - try macho_file.getFile(index).?.object.writeAtomsRelocatable(macho_file); - macho_file.getFile(index).?.writeSymtab(macho_file, macho_file); + const tp = macho_file.base.comp.thread_pool; + var wg: WaitGroup = .{}; + { + wg.reset(); + defer wg.wait(); + + for (macho_file.objects.items) |index| { + tp.spawnWg(&wg, writeAtomsWorker, .{ macho_file, macho_file.getFile(index).? }); + tp.spawnWg(&wg, File.writeSymtab, .{ macho_file.getFile(index).?, macho_file, macho_file }); + } + + if (macho_file.getZigObject()) |zo| { + tp.spawnWg(&wg, writeAtomsWorker, .{ macho_file, zo.asFile() }); + tp.spawnWg(&wg, File.writeSymtab, .{ zo.asFile(), macho_file, macho_file }); + } + + if (macho_file.eh_frame_sect_index) |_| { + tp.spawnWg(&wg, writeEhFrameWorker, .{macho_file}); + } + + if (macho_file.unwind_info_sect_index) |_| { + for (macho_file.objects.items) |index| { + tp.spawnWg(&wg, writeCompactUnwindWorker, .{ macho_file, macho_file.getFile(index).?.object }); + } + } } + if (macho_file.has_errors.swap(false, .seq_cst)) return error.FlushFailure; + if (macho_file.getZigObject()) |zo| { try zo.writeRelocs(macho_file); - try zo.writeAtomsRelocatable(macho_file); - zo.writeSymtab(macho_file, macho_file); - } - - if (macho_file.eh_frame_sect_index) |_| { - try writeEhFrame(macho_file); } +} - if (macho_file.unwind_info_sect_index) |_| { - for (macho_file.objects.items) |index| { - try macho_file.getFile(index).?.object.writeCompactUnwindRelocatable(macho_file); - } - } +fn writeAtomsWorker(macho_file: *MachO, file: File) void { + const tracy = trace(@src()); + defer tracy.end(); + file.writeAtomsRelocatable(macho_file) catch |err| { + macho_file.reportParseError2(file.getIndex(), "failed to write atoms: {s}", .{ + @errorName(err), + }) catch {}; + _ = macho_file.has_errors.swap(true, .seq_cst); + }; } -fn writeEhFrame(macho_file: *MachO) !void { +fn writeEhFrameWorker(macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); const sect_index = macho_file.eh_frame_sect_index.?; const buffer = macho_file.sections.items(.out)[sect_index]; const relocs = macho_file.sections.items(.relocs)[sect_index]; - try eh_frame.writeRelocs(macho_file, buffer.items, relocs.items); + eh_frame.writeRelocs(macho_file, buffer.items, relocs.items) catch |err| { + macho_file.reportUnexpectedError("failed to write '__LD,__eh_frame' section: {s}", .{ + @errorName(err), + }) catch {}; + _ = macho_file.has_errors.swap(true, .seq_cst); + }; +} + +fn writeCompactUnwindWorker(macho_file: *MachO, object: *Object) void { + const tracy = trace(@src()); + defer tracy.end(); + object.writeCompactUnwindRelocatable(macho_file) catch |err| { + macho_file.reportUnexpectedError("failed to write '__LD,__eh_frame' section: {s}", .{ + @errorName(err), + }) catch {}; + _ = macho_file.has_errors.swap(true, .seq_cst); + }; } fn writeSectionsToFile(macho_file: *MachO) !void { @@ -778,3 +785,4 @@ const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const Symbol = @import("Symbol.zig"); +const WaitGroup = std.Thread.WaitGroup; diff --git a/src/link/MachO/synthetic.zig b/src/link/MachO/synthetic.zig index d7316d63b78766ed05128c2264314a11c3258430..5285412bb755de68c5e9127356d976a0edb119d3 100644 --- a/src/link/MachO/synthetic.zig +++ b/src/link/MachO/synthetic.zig @@ -24,8 +24,8 @@ pub const ZigGotSection = struct { const entry = &zig_got.entries.items[index]; entry.* = sym_index; const symbol = &zo.symbols.items[sym_index]; - assert(symbol.flags.needs_zig_got); - symbol.flags.has_zig_got = true; + assert(symbol.getSectionFlags().needs_zig_got); + symbol.setSectionFlags(.{ .has_zig_got = true }); symbol.addExtra(.{ .zig_got = index }, macho_file); return index; } @@ -121,7 +121,7 @@ pub const GotSection = struct { const entry = try got.symbols.addOne(gpa); entry.* = ref; const symbol = ref.getSymbol(macho_file).?; - symbol.flags.has_got = true; + symbol.setSectionFlags(.{ .has_got = true }); symbol.addExtra(.{ .got = index }, macho_file); } @@ -689,7 +689,7 @@ pub const DataInCode = struct { dices[next_dice].offset < end_off) : (next_dice += 1) {} - if (atom.flags.alive) for (dices[start_dice..next_dice]) |d| { + if (atom.isAlive()) for (dices[start_dice..next_dice]) |d| { dice.entries.appendAssumeCapacity(.{ .atom_ref = .{ .index = atom_index, .file = index }, .offset = @intCast(d.offset - start_off), diff --git a/src/link/MachO/thunks.zig b/src/link/MachO/thunks.zig index 2f2bd177e43915624ad77fdbcf501e3e6ec5509f..37013c54c4c2be52360e2a9b9480545c4a2feef9 100644 --- a/src/link/MachO/thunks.zig +++ b/src/link/MachO/thunks.zig @@ -17,7 +17,7 @@ pub fn createThunks(sect_id: u8, macho_file: *MachO) !void { while (i < atoms.len) { const start = i; const start_atom = atoms[start].getAtom(macho_file).?; - assert(start_atom.flags.alive); + assert(start_atom.isAlive()); start_atom.value = advance(header, start_atom.size, start_atom.alignment); i += 1; @@ -25,7 +25,7 @@ pub fn createThunks(sect_id: u8, macho_file: *MachO) !void { header.size - start_atom.value < max_allowed_distance) : (i += 1) { const atom = atoms[i].getAtom(macho_file).?; - assert(atom.flags.alive); + assert(atom.isAlive()); atom.value = advance(header, atom.size, atom.alignment); } @@ -71,7 +71,7 @@ fn scanRelocs(thunk_index: Thunk.Index, gpa: Allocator, atoms: []const MachO.Ref fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { const target = rel.getTargetSymbol(atom.*, macho_file); - if (target.flags.stubs or target.flags.objc_stubs) return false; + if (target.getSectionFlags().stubs or target.getSectionFlags().objc_stubs) return false; if (atom.out_n_sect != target.getOutputSectionIndex(macho_file)) return false; const target_atom = target.getAtom(macho_file).?; if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 6b03962890e63df32c0488aed1788d6bbd64c685..309ed2b467c104959eec06e5f4dfef7e2f31632e 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -658,9 +658,9 @@ fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool { var object = Object.create(wasm, obj_file, path, null) catch |err| switch (err) { error.InvalidMagicByte, error.NotObjectFile => return false, else => |e| { - var err_note = try wasm.addErrorWithNotes(1); - try err_note.addMsg(wasm, "Failed parsing object file: {s}", .{@errorName(e)}); - try err_note.addNote(wasm, "while parsing '{s}'", .{path}); + var err_note = try wasm.base.addErrorWithNotes(1); + try err_note.addMsg("Failed parsing object file: {s}", .{@errorName(e)}); + try err_note.addNote("while parsing '{s}'", .{path}); return error.FlushFailure; }, }; @@ -714,9 +714,9 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool { return false; }, else => |e| { - var err_note = try wasm.addErrorWithNotes(1); - try err_note.addMsg(wasm, "Failed parsing archive: {s}", .{@errorName(e)}); - try err_note.addNote(wasm, "while parsing archive {s}", .{path}); + var err_note = try wasm.base.addErrorWithNotes(1); + try err_note.addMsg("Failed parsing archive: {s}", .{@errorName(e)}); + try err_note.addNote("while parsing archive {s}", .{path}); return error.FlushFailure; }, }; @@ -741,9 +741,9 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool { for (offsets.keys()) |file_offset| { var object = archive.parseObject(wasm, file_offset) catch |e| { - var err_note = try wasm.addErrorWithNotes(1); - try err_note.addMsg(wasm, "Failed parsing object: {s}", .{@errorName(e)}); - try err_note.addNote(wasm, "while parsing object in archive {s}", .{path}); + var err_note = try wasm.base.addErrorWithNotes(1); + try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)}); + try err_note.addNote("while parsing object in archive {s}", .{path}); return error.FlushFailure; }; object.index = @enumFromInt(wasm.files.len); @@ -779,9 +779,9 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { if (symbol.isLocal()) { if (symbol.isUndefined()) { - var err = try wasm.addErrorWithNotes(1); - try err.addMsg(wasm, "Local symbols are not allowed to reference imports", .{}); - try err.addNote(wasm, "symbol '{s}' defined in '{s}'", .{ sym_name, obj_file.path() }); + var err = try wasm.base.addErrorWithNotes(1); + try err.addMsg("Local symbols are not allowed to reference imports", .{}); + try err.addNote("symbol '{s}' defined in '{s}'", .{ sym_name, obj_file.path() }); } try wasm.resolved_symbols.putNoClobber(gpa, location, {}); continue; @@ -816,10 +816,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { break :outer; // existing is weak, while new one isn't. Replace it. } // both are defined and weak, we have a symbol collision. - var err = try wasm.addErrorWithNotes(2); - try err.addMsg(wasm, "symbol '{s}' defined multiple times", .{sym_name}); - try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); - try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); + var err = try wasm.base.addErrorWithNotes(2); + try err.addMsg("symbol '{s}' defined multiple times", .{sym_name}); + try err.addNote("first definition in '{s}'", .{existing_file_path}); + try err.addNote("next definition in '{s}'", .{obj_file.path()}); } try wasm.discarded.put(gpa, location, existing_loc); @@ -827,10 +827,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { } if (symbol.tag != existing_sym.tag) { - var err = try wasm.addErrorWithNotes(2); - try err.addMsg(wasm, "symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) }); - try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); - try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); + var err = try wasm.base.addErrorWithNotes(2); + try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) }); + try err.addNote("first definition in '{s}'", .{existing_file_path}); + try err.addNote("next definition in '{s}'", .{obj_file.path()}); } if (existing_sym.isUndefined() and symbol.isUndefined()) { @@ -847,14 +847,14 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { const imp = obj_file.import(sym_index); const module_name = obj_file.string(imp.module_name); if (!mem.eql(u8, existing_name, module_name)) { - var err = try wasm.addErrorWithNotes(2); - try err.addMsg(wasm, "symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ + var err = try wasm.base.addErrorWithNotes(2); + try err.addMsg("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ sym_name, existing_name, module_name, }); - try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); - try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); + try err.addNote("first definition in '{s}'", .{existing_file_path}); + try err.addNote("next definition in '{s}'", .{obj_file.path()}); } } @@ -867,10 +867,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { const existing_ty = wasm.getGlobalType(existing_loc); const new_ty = wasm.getGlobalType(location); if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) { - var err = try wasm.addErrorWithNotes(2); - try err.addMsg(wasm, "symbol '{s}' mismatching global types", .{sym_name}); - try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); - try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); + var err = try wasm.base.addErrorWithNotes(2); + try err.addMsg("symbol '{s}' mismatching global types", .{sym_name}); + try err.addNote("first definition in '{s}'", .{existing_file_path}); + try err.addNote("next definition in '{s}'", .{obj_file.path()}); } } @@ -878,11 +878,11 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { const existing_ty = wasm.getFunctionSignature(existing_loc); const new_ty = wasm.getFunctionSignature(location); if (!existing_ty.eql(new_ty)) { - var err = try wasm.addErrorWithNotes(3); - try err.addMsg(wasm, "symbol '{s}' mismatching function signatures.", .{sym_name}); - try err.addNote(wasm, "expected signature {}, but found signature {}", .{ existing_ty, new_ty }); - try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); - try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); + var err = try wasm.base.addErrorWithNotes(3); + try err.addMsg("symbol '{s}' mismatching function signatures.", .{sym_name}); + try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty }); + try err.addNote("first definition in '{s}'", .{existing_file_path}); + try err.addNote("next definition in '{s}'", .{obj_file.path()}); } } @@ -930,9 +930,9 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { // Parse object and and resolve symbols again before we check remaining // undefined symbols. var object = archive.parseObject(wasm, offset.items[0]) catch |e| { - var err_note = try wasm.addErrorWithNotes(1); - try err_note.addMsg(wasm, "Failed parsing object: {s}", .{@errorName(e)}); - try err_note.addNote(wasm, "while parsing object in archive {s}", .{archive.name}); + var err_note = try wasm.base.addErrorWithNotes(1); + try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)}); + try err_note.addNote("while parsing object in archive {s}", .{archive.name}); return error.FlushFailure; }; object.index = @enumFromInt(wasm.files.len); @@ -1237,9 +1237,9 @@ fn validateFeatures( allowed[used_index] = is_enabled; emit_features_count.* += @intFromBool(is_enabled); } else if (is_enabled and !allowed[used_index]) { - var err = try wasm.addErrorWithNotes(1); - try err.addMsg(wasm, "feature '{}' not allowed, but used by linked object", .{@as(types.Feature.Tag, @enumFromInt(used_index))}); - try err.addNote(wasm, "defined in '{s}'", .{wasm.files.items(.data)[used_set >> 1].object.path}); + var err = try wasm.base.addErrorWithNotes(1); + try err.addMsg("feature '{}' not allowed, but used by linked object", .{@as(types.Feature.Tag, @enumFromInt(used_index))}); + try err.addNote("defined in '{s}'", .{wasm.files.items(.data)[used_set >> 1].object.path}); valid_feature_set = false; } } @@ -1251,7 +1251,8 @@ fn validateFeatures( if (shared_memory) { const disallowed_feature = disallowed[@intFromEnum(types.Feature.Tag.shared_mem)]; if (@as(u1, @truncate(disallowed_feature)) != 0) { - try wasm.addErrorWithoutNotes( + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg( "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}, ); @@ -1260,7 +1261,8 @@ fn validateFeatures( for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| { if (!allowed[@intFromEnum(feature)]) { - try wasm.addErrorWithoutNotes("feature '{}' is not used but is required for shared-memory", .{feature}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("feature '{}' is not used but is required for shared-memory", .{feature}); } } } @@ -1268,7 +1270,8 @@ fn validateFeatures( if (has_tls) { for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| { if (!allowed[@intFromEnum(feature)]) { - try wasm.addErrorWithoutNotes("feature '{}' is not used but is required for thread-local storage", .{feature}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("feature '{}' is not used but is required for thread-local storage", .{feature}); } } } @@ -1281,10 +1284,10 @@ fn validateFeatures( // from here a feature is always used const disallowed_feature = disallowed[@intFromEnum(feature.tag)]; if (@as(u1, @truncate(disallowed_feature)) != 0) { - var err = try wasm.addErrorWithNotes(2); - try err.addMsg(wasm, "feature '{}' is disallowed, but used by linked object", .{feature.tag}); - try err.addNote(wasm, "disallowed by '{s}'", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}); - try err.addNote(wasm, "used in '{s}'", .{object.path}); + var err = try wasm.base.addErrorWithNotes(2); + try err.addMsg("feature '{}' is disallowed, but used by linked object", .{feature.tag}); + try err.addNote("disallowed by '{s}'", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}); + try err.addNote("used in '{s}'", .{object.path}); valid_feature_set = false; } @@ -1295,10 +1298,10 @@ fn validateFeatures( for (required, 0..) |required_feature, feature_index| { const is_required = @as(u1, @truncate(required_feature)) != 0; if (is_required and !object_used_features[feature_index]) { - var err = try wasm.addErrorWithNotes(2); - try err.addMsg(wasm, "feature '{}' is required but not used in linked object", .{@as(types.Feature.Tag, @enumFromInt(feature_index))}); - try err.addNote(wasm, "required by '{s}'", .{wasm.files.items(.data)[required_feature >> 1].object.path}); - try err.addNote(wasm, "missing in '{s}'", .{object.path}); + var err = try wasm.base.addErrorWithNotes(2); + try err.addMsg("feature '{}' is required but not used in linked object", .{@as(types.Feature.Tag, @enumFromInt(feature_index))}); + try err.addNote("required by '{s}'", .{wasm.files.items(.data)[required_feature >> 1].object.path}); + try err.addNote("missing in '{s}'", .{object.path}); valid_feature_set = false; } } @@ -1376,9 +1379,9 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void { else wasm.name; const symbol_name = undef.getName(wasm); - var err = try wasm.addErrorWithNotes(1); - try err.addMsg(wasm, "could not resolve undefined symbol '{s}'", .{symbol_name}); - try err.addNote(wasm, "defined in '{s}'", .{file_name}); + var err = try wasm.base.addErrorWithNotes(1); + try err.addMsg("could not resolve undefined symbol '{s}'", .{symbol_name}); + try err.addNote("defined in '{s}'", .{file_name}); } } if (found_undefined_symbols) { @@ -1757,7 +1760,8 @@ fn setupInitFunctions(wasm: *Wasm) !void { break :ty object.func_types[func.type_index]; }; if (ty.params.len != 0) { - try wasm.addErrorWithoutNotes("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)}); } log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)}); wasm.init_funcs.appendAssumeCapacity(.{ @@ -2140,7 +2144,8 @@ fn checkExportNames(wasm: *Wasm) !void { for (force_exp_names) |exp_name| { const loc = wasm.findGlobalSymbol(exp_name) orelse { - try wasm.addErrorWithoutNotes("could not export '{s}', symbol not found", .{exp_name}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("could not export '{s}', symbol not found", .{exp_name}); failed_exports = true; continue; }; @@ -2203,13 +2208,15 @@ fn setupStart(wasm: *Wasm) !void { const entry_name = wasm.entry_name orelse return; const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse { - try wasm.addErrorWithoutNotes("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name}); return error.FlushFailure; }; const symbol = symbol_loc.getSymbol(wasm); if (symbol.tag != .function) { - try wasm.addErrorWithoutNotes("Entry symbol '{s}' is not a function", .{entry_name}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Entry symbol '{s}' is not a function", .{entry_name}); return error.FlushFailure; } @@ -2314,13 +2321,16 @@ fn setupMemory(wasm: *Wasm) !void { if (wasm.initial_memory) |initial_memory| { if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) { - try wasm.addErrorWithoutNotes("Initial memory must be {d}-byte aligned", .{page_size}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Initial memory must be {d}-byte aligned", .{page_size}); } if (memory_ptr > initial_memory) { - try wasm.addErrorWithoutNotes("Initial memory too small, must be at least {d} bytes", .{memory_ptr}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Initial memory too small, must be at least {d} bytes", .{memory_ptr}); } if (initial_memory > max_memory_allowed) { - try wasm.addErrorWithoutNotes("Initial memory exceeds maximum memory {d}", .{max_memory_allowed}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Initial memory exceeds maximum memory {d}", .{max_memory_allowed}); } memory_ptr = initial_memory; } @@ -2337,13 +2347,16 @@ fn setupMemory(wasm: *Wasm) !void { if (wasm.max_memory) |max_memory| { if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) { - try wasm.addErrorWithoutNotes("Maximum memory must be {d}-byte aligned", .{page_size}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Maximum memory must be {d}-byte aligned", .{page_size}); } if (memory_ptr > max_memory) { - try wasm.addErrorWithoutNotes("Maximum memory too small, must be at least {d} bytes", .{memory_ptr}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Maximum memory too small, must be at least {d} bytes", .{memory_ptr}); } if (max_memory > max_memory_allowed) { - try wasm.addErrorWithoutNotes("Maximum memory exceeds maximum amount {d}", .{max_memory_allowed}); + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("Maximum memory exceeds maximum amount {d}", .{max_memory_allowed}); } wasm.memories.limits.max = @as(u32, @intCast(max_memory / page_size)); wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX); @@ -2446,9 +2459,9 @@ pub fn getMatchingSegment(wasm: *Wasm, file_index: File.Index, symbol_index: Sym break :blk index; }; } else { - var err = try wasm.addErrorWithNotes(1); - try err.addMsg(wasm, "found unknown section '{s}'", .{section_name}); - try err.addNote(wasm, "defined in '{s}'", .{obj_file.path()}); + var err = try wasm.base.addErrorWithNotes(1); + try err.addMsg("found unknown section '{s}'", .{section_name}); + try err.addNote("defined in '{s}'", .{obj_file.path()}); return error.UnexpectedValue; } }, @@ -2564,23 +2577,23 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no if (wasm.zig_object_index != .null) { try wasm.resolveSymbolsInObject(wasm.zig_object_index); } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (wasm.base.hasErrors()) return error.FlushFailure; for (wasm.objects.items) |object_index| { try wasm.resolveSymbolsInObject(object_index); } - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (wasm.base.hasErrors()) return error.FlushFailure; var emit_features_count: u32 = 0; var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; try wasm.validateFeatures(&enabled_features, &emit_features_count); try wasm.resolveSymbolsInArchives(); - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (wasm.base.hasErrors()) return error.FlushFailure; try wasm.resolveLazySymbols(); try wasm.checkUndefinedSymbols(); try wasm.checkExportNames(); try wasm.setupInitFunctions(); - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (wasm.base.hasErrors()) return error.FlushFailure; try wasm.setupStart(); try wasm.markReferences(); @@ -2589,7 +2602,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no try wasm.mergeTypes(); try wasm.allocateAtoms(); try wasm.setupMemory(); - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (wasm.base.hasErrors()) return error.FlushFailure; wasm.allocateVirtualAddresses(); wasm.mapFunctionTable(); try wasm.initializeCallCtorsFunction(); @@ -2599,7 +2612,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no try wasm.setupStartSection(); try wasm.setupExports(); try wasm.writeToFile(enabled_features, emit_features_count, arena); - if (comp.link_errors.items.len > 0) return error.FlushFailure; + if (wasm.base.hasErrors()) return error.FlushFailure; } /// Writes the WebAssembly in-memory module to the file @@ -2997,7 +3010,10 @@ fn writeToFile( }) catch unreachable; try emitBuildIdSection(&binary_bytes, str); }, - else => |mode| try wasm.addErrorWithoutNotes("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)}), + else => |mode| { + var err = try wasm.base.addErrorWithNotes(0); + try err.addMsg("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)}); + }, } var debug_bytes = std.ArrayList(u8).init(gpa); @@ -4086,57 +4102,3 @@ fn defaultEntrySymbolName(wasi_exec_model: std.builtin.WasiExecModel) []const u8 .command => "_start", }; } - -const ErrorWithNotes = struct { - /// Allocated index in comp.link_errors array. - index: usize, - - /// Next available note slot. - note_slot: usize = 0, - - pub fn addMsg( - err: ErrorWithNotes, - wasm_file: *const Wasm, - comptime format: []const u8, - args: anytype, - ) error{OutOfMemory}!void { - const comp = wasm_file.base.comp; - const gpa = comp.gpa; - const err_msg = &comp.link_errors.items[err.index]; - err_msg.msg = try std.fmt.allocPrint(gpa, format, args); - } - - pub fn addNote( - err: *ErrorWithNotes, - wasm_file: *const Wasm, - comptime format: []const u8, - args: anytype, - ) error{OutOfMemory}!void { - const comp = wasm_file.base.comp; - const gpa = comp.gpa; - const err_msg = &comp.link_errors.items[err.index]; - err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) }; - err.note_slot += 1; - } -}; - -pub fn addErrorWithNotes(wasm: *const Wasm, note_count: usize) error{OutOfMemory}!ErrorWithNotes { - const comp = wasm.base.comp; - const gpa = comp.gpa; - try comp.link_errors.ensureUnusedCapacity(gpa, 1); - return wasm.addErrorWithNotesAssumeCapacity(note_count); -} - -pub fn addErrorWithoutNotes(wasm: *const Wasm, comptime fmt: []const u8, args: anytype) !void { - const err = try wasm.addErrorWithNotes(0); - try err.addMsg(wasm, fmt, args); -} - -fn addErrorWithNotesAssumeCapacity(wasm: *const Wasm, note_count: usize) error{OutOfMemory}!ErrorWithNotes { - const comp = wasm.base.comp; - const gpa = comp.gpa; - const index = comp.link_errors.items.len; - const err = comp.link_errors.addOneAssumeCapacity(); - err.* = .{ .msg = undefined, .notes = try gpa.alloc(link.File.ErrorMsg, note_count) }; - return .{ .index = index }; -} diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index c2f5739404461d0a966ddd2daf04fc0b34a62270..06512ae97e22b928cfd79dd4d1c2cf8b10501ab0 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -235,27 +235,27 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S if (object.imported_tables_count == table_count) return null; if (table_count != 0) { - var err = try wasm_file.addErrorWithNotes(1); - try err.addMsg(wasm_file, "Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{ + var err = try wasm_file.base.addErrorWithNotes(1); + try err.addMsg("Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{ object.imported_tables_count, table_count, }); - try err.addNote(wasm_file, "defined in '{s}'", .{object.path}); + try err.addNote("defined in '{s}'", .{object.path}); return error.MissingTableSymbols; } // MVP object files cannot have any table definitions, only imports (for the indirect function table). if (object.tables.len > 0) { - var err = try wasm_file.addErrorWithNotes(1); - try err.addMsg(wasm_file, "Unexpected table definition without representing table symbols.", .{}); - try err.addNote(wasm_file, "defined in '{s}'", .{object.path}); + var err = try wasm_file.base.addErrorWithNotes(1); + try err.addMsg("Unexpected table definition without representing table symbols.", .{}); + try err.addNote("defined in '{s}'", .{object.path}); return error.UnexpectedTable; } if (object.imported_tables_count != 1) { - var err = try wasm_file.addErrorWithNotes(1); - try err.addMsg(wasm_file, "Found more than one table import, but no representing table symbols", .{}); - try err.addNote(wasm_file, "defined in '{s}'", .{object.path}); + var err = try wasm_file.base.addErrorWithNotes(1); + try err.addMsg("Found more than one table import, but no representing table symbols", .{}); + try err.addNote("defined in '{s}'", .{object.path}); return error.MissingTableSymbols; } @@ -266,9 +266,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S } else unreachable; if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) { - var err = try wasm_file.addErrorWithNotes(1); - try err.addMsg(wasm_file, "Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)}); - try err.addNote(wasm_file, "defined in '{s}'", .{object.path}); + var err = try wasm_file.base.addErrorWithNotes(1); + try err.addMsg("Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)}); + try err.addNote("defined in '{s}'", .{object.path}); return error.MissingTableSymbols; } @@ -596,9 +596,9 @@ fn Parser(comptime ReaderType: type) type { try reader.readNoEof(name); const tag = types.known_features.get(name) orelse { - var err = try parser.wasm_file.addErrorWithNotes(1); - try err.addMsg(parser.wasm_file, "Object file contains unknown feature: {s}", .{name}); - try err.addNote(parser.wasm_file, "defined in '{s}'", .{parser.object.path}); + var err = try parser.wasm_file.base.addErrorWithNotes(1); + try err.addMsg("Object file contains unknown feature: {s}", .{name}); + try err.addNote("defined in '{s}'", .{parser.object.path}); return error.UnknownFeature; }; feature.* = .{ diff --git a/src/link/tapi.zig b/src/link/tapi.zig index 6fc62e585dca328a98d9f65b838b6e3942502f9f..1ebd0066366248b1f521d0606774e5555db171cc 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -129,8 +129,8 @@ pub const Tbd = union(enum) { pub const TapiError = error{ NotLibStub, - FileTooBig, -} || yaml.YamlError || std.fs.File.ReadError; + InputOutput, +} || yaml.YamlError || std.fs.File.PReadError; pub const LibStub = struct { /// Underlying memory for stub's contents. @@ -140,8 +140,14 @@ pub const LibStub = struct { inner: []Tbd, pub fn loadFromFile(allocator: Allocator, file: fs.File) TapiError!LibStub { - const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32)); + const filesize = blk: { + const stat = file.stat() catch break :blk std.math.maxInt(u32); + break :blk @min(stat.size, std.math.maxInt(u32)); + }; + const source = try allocator.alloc(u8, filesize); defer allocator.free(source); + const amt = try file.preadAll(source, 0); + if (amt != filesize) return error.InputOutput; var lib_stub = LibStub{ .yaml = try Yaml.load(allocator, source), diff --git a/src/main.zig b/src/main.zig index 561dd2f28e2a0dd26005668c8b210a3d581ff1ae..a715b36b5cd193f851831ec3c0b477d12304c924 100644 --- a/src/main.zig +++ b/src/main.zig @@ -211,6 +211,9 @@ fn verifyLibcxxCorrectlyLinked() void { } fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { + const tr = tracy.trace(@src()); + defer tr.end(); + if (args.len <= 1) { std.log.info("{s}", .{usage}); fatal("expected command argument", .{});