diff options
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/C.zig | 7 | ||||
| -rw-r--r-- | src/link/Coff.zig | 630 | ||||
| -rw-r--r-- | src/link/Dwarf.zig | 2 | ||||
| -rw-r--r-- | src/link/Elf.zig | 781 | ||||
| -rw-r--r-- | src/link/Elf/ZigObject.zig | 8 | ||||
| -rw-r--r-- | src/link/Goff.zig | 5 | ||||
| -rw-r--r-- | src/link/Lld.zig | 2148 | ||||
| -rw-r--r-- | src/link/MachO.zig | 16 | ||||
| -rw-r--r-- | src/link/MachO/DebugSymbols.zig | 2 | ||||
| -rw-r--r-- | src/link/MachO/ZigObject.zig | 8 | ||||
| -rw-r--r-- | src/link/Plan9.zig | 40 | ||||
| -rw-r--r-- | src/link/SpirV.zig | 11 | ||||
| -rw-r--r-- | src/link/Wasm.zig | 473 | ||||
| -rw-r--r-- | src/link/Xcoff.zig | 5 |
14 files changed, 2199 insertions, 1937 deletions
diff --git a/src/link/C.zig b/src/link/C.zig index 15004a26b7..34fc1d3775 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -145,7 +145,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = file, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, }; @@ -381,10 +380,6 @@ pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedIn _ = ti_id; } -pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { const gpa = self.base.comp.gpa; var defines = std.ArrayList(u8).init(gpa); @@ -400,7 +395,7 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { return defines; } -pub fn flushZcu(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { +pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = arena; // Has the same lifetime as the call to Compilation.update. const tracy = trace(@src()); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 12a9dc9753..e7dcbcdf2a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,23 +1,14 @@ -//! The main driver of the COFF linker. -//! Currently uses our own implementation for the incremental linker, and falls back to -//! LLD for traditional linking (linking relocatable object files). -//! LLD is also the default linker for LLVM. +//! The main driver of the self-hosted COFF linker. base: link.File, image_base: u64, -subsystem: ?std.Target.SubSystem, -tsaware: bool, -nxcompat: bool, -dynamicbase: bool, /// TODO this and minor_subsystem_version should be combined into one property and left as /// default or populated together. They should not be separate fields. major_subsystem_version: u16, minor_subsystem_version: u16, -lib_directories: []const Directory, entry: link.File.OpenOptions.Entry, entry_addr: ?u32, module_definition_file: ?[]const u8, -pdb_out_path: ?[]const u8, repro: bool, ptr_width: PtrWidth, @@ -84,16 +75,6 @@ base_relocs: BaseRelocationTable = .{}, /// Hot-code swapping state. hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, -/// When linking with LLD, these flags are used to determine the subsystem to pass on the LLD command line. -lld_export_flags: struct { - c_main: bool = false, - winmain: bool = false, - wwinmain: bool = false, - winmain_crt_startup: bool = false, - wwinmain_crt_startup: bool = false, - dllmain_crt_startup: bool = false, -} = .{}, - const is_hot_update_compatible = switch (builtin.target.os.tag) { .windows => true, else => false, @@ -233,7 +214,6 @@ pub fn createEmpty( const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; const use_llvm = comp.config.use_llvm; - const use_lld = build_options.have_llvm and comp.config.use_lld; const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { 0...32 => .p32, @@ -244,12 +224,10 @@ pub fn createEmpty( else => 0x1000, }; - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. // If using LLVM to generate the object file for the zig compilation unit, // we need a place to put the object file so that it can be subsequently // handled. - const zcu_object_sub_path = if (!use_lld and !use_llvm) + const zcu_object_sub_path = if (!use_llvm) null else try allocPrint(arena, "{s}.obj", .{emit.sub_path}); @@ -266,7 +244,6 @@ pub fn createEmpty( .print_gc_sections = options.print_gc_sections, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .ptr_width = ptr_width, @@ -291,39 +268,21 @@ pub fn createEmpty( .Obj => 0, }, - // Subsystem depends on the set of public symbol names from linked objects. - // See LinkerDriver::inferSubsystem from the LLD project for the flow chart. - .subsystem = options.subsystem, - .entry = options.entry, - .tsaware = options.tsaware, - .nxcompat = options.nxcompat, - .dynamicbase = options.dynamicbase, .major_subsystem_version = options.major_subsystem_version orelse 6, .minor_subsystem_version = options.minor_subsystem_version orelse 0, - .lib_directories = options.lib_directories, .entry_addr = math.cast(u32, options.entry_addr orelse 0) orelse return error.EntryAddressTooBig, .module_definition_file = options.module_definition_file, - .pdb_out_path = options.pdb_out_path, .repro = options.repro, }; errdefer coff.base.destroy(); - if (use_lld and (use_llvm or !comp.config.have_zcu)) { - // LLVM emits the object file (if any); LLD links it into the final product. - return coff; - } - - // What path should this COFF linker code output to? - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. - const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; - coff.base.file = try emit.root_dir.handle.createFile(sub_path, .{ + coff.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(use_lld, output_mode, link_mode), + .mode = link.File.determineMode(output_mode, link_mode), }); const gpa = comp.gpa; @@ -1327,7 +1286,7 @@ pub fn getOrCreateAtomForLazySymbol( } state_ptr.* = .pending_flush; const atom = atom_ptr.*; - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try coff.updateLazySymbolAtom(pt, lazy_sym, atom, switch (lazy_sym.kind) { .code => coff.text_section_index.?, .const_data => coff.rdata_section_index.?, @@ -1631,575 +1590,7 @@ fn resolveGlobalSymbol(coff: *Coff, current: SymbolWithLoc) !void { gop.value_ptr.* = current; } -pub fn flush(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - const comp = coff.base.comp; - const use_lld = build_options.have_llvm and comp.config.use_lld; - const diags = &comp.link_diags; - if (use_lld) { - return coff.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), - }; - } - switch (comp.config.output_mode) { - .Exe, .Obj => return coff.flushZcu(arena, tid, prog_node), - .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}), - } -} - -fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { - dev.check(.lld_linker); - - const tracy = trace(@src()); - defer tracy.end(); - - const comp = coff.base.comp; - const gpa = comp.gpa; - - const directory = coff.base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{coff.base.emit.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { - if (zcu.llvm_object == null) { - try coff.flushZcu(arena, tid, prog_node); - } else { - // `Compilation.flush` has already made LLVM emit this object file for us. - } - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, coff.base.zcu_object_sub_path.? }); - } else { - break :blk coff.base.zcu_object_sub_path.?; - } - } else null; - - const sub_prog_node = prog_node.start("LLD Link", 0); - defer sub_prog_node.end(); - - const is_lib = comp.config.output_mode == .Lib; - const is_dyn_lib = comp.config.link_mode == .dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or comp.config.output_mode == .Exe; - const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib; - const target = comp.root_mod.resolved_target.result; - const optimize_mode = comp.root_mod.optimize_mode; - const entry_name: ?[]const u8 = switch (coff.entry) { - // This logic isn't quite right for disabled or enabled. No point in fixing it - // when the goal is to eliminate dependency on LLD anyway. - // https://github.com/ziglang/zig/issues/17751 - .disabled, .default, .enabled => null, - .named => |name| name, - }; - - // See link/Elf.zig for comments on how this mechanism works. - const id_symlink_basename = "lld.id"; - - var man: Cache.Manifest = undefined; - defer if (!coff.base.disable_lld_caching) man.deinit(); - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!coff.base.disable_lld_caching) { - man = comp.cache_parent.obtain(); - coff.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 14); - - try link.hashInputs(&man, comp.link_inputs); - for (comp.c_object_table.keys()) |key| { - _ = try man.addFilePath(key.status.success.object_path, null); - } - for (comp.win32_resource_table.keys()) |key| { - _ = try man.addFile(key.status.success.res_path, null); - } - try man.addOptionalFile(module_obj_path); - man.hash.addOptionalBytes(entry_name); - man.hash.add(coff.base.stack_size); - man.hash.add(coff.image_base); - man.hash.add(coff.base.build_id); - { - // TODO remove this, libraries must instead be resolved by the frontend. - for (coff.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path); - } - man.hash.add(comp.skip_linker_dependencies); - if (comp.config.link_libc) { - man.hash.add(comp.libc_installation != null); - if (comp.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - if (target.abi == .msvc or target.abi == .itanium) { - man.hash.addBytes(libc_installation.msvc_lib_dir.?); - man.hash.addBytes(libc_installation.kernel32_lib_dir.?); - } - } - } - man.hash.addListOfBytes(comp.windows_libs.keys()); - man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); - man.hash.addOptional(coff.subsystem); - man.hash.add(comp.config.is_test); - man.hash.add(coff.tsaware); - man.hash.add(coff.nxcompat); - man.hash.add(coff.dynamicbase); - man.hash.add(coff.base.allow_shlib_undefined); - // strip does not need to go into the linker hash because it is part of the hash namespace - man.hash.add(coff.major_subsystem_version); - man.hash.add(coff.minor_subsystem_version); - man.hash.add(coff.repro); - man.hash.addOptional(comp.version); - try man.addOptionalFile(coff.module_definition_file); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - coff.base.lock = man.toOwnedLock(); - return; - } - log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - if (comp.config.output_mode == .Obj) { - // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk Path.initCwd(p); - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - try std.fs.Dir.copyFile( - the_object_path.root_dir.handle, - the_object_path.sub_path, - directory.handle, - coff.base.emit.sub_path, - .{}, - ); - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(gpa); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - const linker_command = "lld-link"; - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); - - if (target.isMinGW()) { - try argv.append("-lldmingw"); - } - - try argv.append("-ERRORLIMIT:0"); - try argv.append("-NOLOGO"); - if (comp.config.debug_format != .strip) { - try argv.append("-DEBUG"); - - const out_ext = std.fs.path.extension(full_out_path); - const out_pdb = coff.pdb_out_path orelse try allocPrint(arena, "{s}.pdb", .{ - full_out_path[0 .. full_out_path.len - out_ext.len], - }); - const out_pdb_basename = std.fs.path.basename(out_pdb); - - try argv.append(try allocPrint(arena, "-PDB:{s}", .{out_pdb})); - try argv.append(try allocPrint(arena, "-PDBALTPATH:{s}", .{out_pdb_basename})); - } - if (comp.version) |version| { - try argv.append(try allocPrint(arena, "-VERSION:{}.{}", .{ version.major, version.minor })); - } - - if (target_util.llvmMachineAbi(target)) |mabi| { - try argv.append(try allocPrint(arena, "-MLLVM:-target-abi={s}", .{mabi})); - } - - try argv.append(try allocPrint(arena, "-MLLVM:-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"})); - - if (comp.config.lto != .none) { - switch (optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-OPT:lldlto=2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), - } - } - if (comp.config.output_mode == .Exe) { - try argv.append(try allocPrint(arena, "-STACK:{d}", .{coff.base.stack_size})); - } - try argv.append(try allocPrint(arena, "-BASE:{d}", .{coff.image_base})); - - switch (coff.base.build_id) { - .none => try argv.append("-BUILD-ID:NO"), - .fast => try argv.append("-BUILD-ID"), - .uuid, .sha1, .md5, .hexstring => {}, - } - - if (target.cpu.arch == .x86) { - try argv.append("-MACHINE:X86"); - } else if (target.cpu.arch == .x86_64) { - try argv.append("-MACHINE:X64"); - } else if (target.cpu.arch == .thumb) { - try argv.append("-MACHINE:ARM"); - } else if (target.cpu.arch == .aarch64) { - try argv.append("-MACHINE:ARM64"); - } - - for (comp.force_undefined_symbols.keys()) |symbol| { - try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); - } - - if (is_dyn_lib) { - try argv.append("-DLL"); - } - - if (entry_name) |name| { - try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{name})); - } - - if (coff.repro) { - try argv.append("-BREPRO"); - } - - if (coff.tsaware) { - try argv.append("-tsaware"); - } - if (coff.nxcompat) { - try argv.append("-nxcompat"); - } - if (!coff.dynamicbase) { - try argv.append("-dynamicbase:NO"); - } - if (coff.base.allow_shlib_undefined) { - try argv.append("-FORCE:UNRESOLVED"); - } - - try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); - - if (comp.implib_emit) |emit| { - const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); - } - - if (comp.config.link_libc) { - if (comp.libc_installation) |libc_installation| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); - - if (target.abi == .msvc or target.abi == .itanium) { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); - } - } - } - - for (coff.lib_directories) |lib_directory| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_directory.path orelse "."})); - } - - try argv.ensureUnusedCapacity(comp.link_inputs.len); - for (comp.link_inputs) |link_input| switch (link_input) { - .dso_exact => unreachable, // not applicable to PE/COFF - inline .dso, .res => |x| { - argv.appendAssumeCapacity(try x.path.toString(arena)); - }, - .object, .archive => |obj| { - if (obj.must_link) { - argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Path, obj.path)})); - } else { - argv.appendAssumeCapacity(try obj.path.toString(arena)); - } - }, - }; - - for (comp.c_object_table.keys()) |key| { - try argv.append(try key.status.success.object_path.toString(arena)); - } - - for (comp.win32_resource_table.keys()) |key| { - try argv.append(key.status.success.res_path); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - if (coff.module_definition_file) |def| { - try argv.append(try allocPrint(arena, "-DEF:{s}", .{def})); - } - - const resolved_subsystem: ?std.Target.SubSystem = blk: { - if (coff.subsystem) |explicit| break :blk explicit; - switch (target.os.tag) { - .windows => { - if (comp.zcu != null) { - if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib) - break :blk null; - if (coff.lld_export_flags.c_main or comp.config.is_test or - coff.lld_export_flags.winmain_crt_startup or - coff.lld_export_flags.wwinmain_crt_startup) - { - break :blk .Console; - } - if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain) - break :blk .Windows; - } - }, - .uefi => break :blk .EfiApplication, - else => {}, - } - break :blk null; - }; - - const Mode = enum { uefi, win32 }; - const mode: Mode = mode: { - if (resolved_subsystem) |subsystem| { - const subsystem_suffix = try allocPrint(arena, ",{d}.{d}", .{ - coff.major_subsystem_version, coff.minor_subsystem_version, - }); - - switch (subsystem) { - .Console => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .EfiApplication => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiBootServiceDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRom => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRuntimeDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .Native => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Posix => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Windows => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - } - } else if (target.os.tag == .uefi) { - break :mode .uefi; - } else { - break :mode .win32; - } - }; - - switch (mode) { - .uefi => try argv.appendSlice(&[_][]const u8{ - "-BASE:0", - "-ENTRY:EfiMain", - "-OPT:REF", - "-SAFESEH:NO", - "-MERGE:.rdata=.data", - "-NODEFAULTLIB", - "-SECTION:.xdata,D", - }), - .win32 => { - if (link_in_crt) { - if (target.abi.isGnu()) { - if (target.cpu.arch == .x86) { - try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); - } else { - try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); - } - - if (is_dyn_lib) { - try argv.append(try comp.crtFileAsString(arena, "dllcrt2.obj")); - if (target.cpu.arch == .x86) { - try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); - } else { - try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); - } - } else { - try argv.append(try comp.crtFileAsString(arena, "crt2.obj")); - } - - try argv.append(try comp.crtFileAsString(arena, "libmingw32.lib")); - } else { - try argv.append(switch (comp.config.link_mode) { - .static => "libcmt.lib", - .dynamic => "msvcrt.lib", - }); - - const lib_str = switch (comp.config.link_mode) { - .static => "lib", - .dynamic => "", - }; - try argv.append(try allocPrint(arena, "{s}vcruntime.lib", .{lib_str})); - try argv.append(try allocPrint(arena, "{s}ucrt.lib", .{lib_str})); - - //Visual C++ 2015 Conformance Changes - //https://msdn.microsoft.com/en-us/library/bb531344.aspx - try argv.append("legacy_stdio_definitions.lib"); - - // msvcrt depends on kernel32 and ntdll - try argv.append("kernel32.lib"); - try argv.append("ntdll.lib"); - } - } else { - try argv.append("-NODEFAULTLIB"); - if (!is_lib and entry_name == null) { - if (comp.zcu != null) { - if (coff.lld_export_flags.winmain_crt_startup) { - try argv.append("-ENTRY:WinMainCRTStartup"); - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } - } - }, - } - - if (comp.config.link_libc and link_in_crt) { - if (comp.zigc_static_lib) |zigc| { - try argv.append(try zigc.full_object_path.toString(arena)); - } - } - - // libc++ dep - if (comp.config.link_libcpp) { - try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); - try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); - } - - // libunwind dep - if (comp.config.link_libunwind) { - try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); - } - - if (comp.config.any_fuzz) { - try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); - } - - const ubsan_rt_path: ?Path = blk: { - if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; - if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - if (ubsan_rt_path) |path| { - try argv.append(try path.toString(arena)); - } - - if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { - // MSVC compiler_rt is missing some stuff, so we build it unconditionally but - // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. - if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); - if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); - } - - try argv.ensureUnusedCapacity(comp.windows_libs.count()); - for (comp.windows_libs.keys()) |key| { - const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); - if (comp.crt_files.get(lib_basename)) |crt_file| { - argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); - continue; - } - if (try findLib(arena, lib_basename, coff.lib_directories)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - if (target.abi.isGnu()) { - const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try findLib(arena, fallback_name, coff.lib_directories)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - } - if (target.abi == .msvc or target.abi == .itanium) { - argv.appendAssumeCapacity(lib_basename); - continue; - } - - log.err("DLL import library for -l{s} not found", .{key}); - return error.DllImportLibraryNotFound; - } - - try link.spawnLld(comp, arena, argv.items); - } - - if (!coff.base.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - coff.base.lock = man.toOwnedLock(); - } -} - -fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Directory) !?[]const u8 { - for (lib_directories) |lib_directory| { - lib_directory.handle.access(name, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - return try lib_directory.join(arena, &.{name}); - } - return null; -} - -pub fn flushZcu( +pub fn flush( coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, @@ -2211,17 +1602,22 @@ pub fn flushZcu( const comp = coff.base.comp; const diags = &comp.link_diags; + switch (coff.base.comp.config.output_mode) { + .Exe, .Obj => {}, + .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}), + } + const sub_prog_node = prog_node.start("COFF Flush", 0); defer sub_prog_node.end(); - return flushZcuInner(coff, arena, tid) catch |err| switch (err) { + return flushInner(coff, arena, tid) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("COFF flush failed: {s}", .{@errorName(e)}), }; } -fn flushZcuInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void { +fn flushInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void { _ = arena; const comp = coff.base.comp; diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index c0d1281df2..393cd53774 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -4391,7 +4391,7 @@ fn refAbbrevCode(dwarf: *Dwarf, abbrev_code: AbbrevCode) UpdateError!@typeInfo(A return @intFromEnum(abbrev_code); } -pub fn flushZcu(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { +pub fn flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index b18fc7ce33..1702ef200c 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -4,7 +4,6 @@ base: link.File, zig_object: ?*ZigObject, rpath_table: std.StringArrayHashMapUnmanaged(void), image_base: u64, -emit_relocs: bool, z_nodelete: bool, z_notext: bool, z_defs: bool, @@ -16,18 +15,7 @@ z_relro: bool, z_common_page_size: ?u64, /// TODO make this non optional and resolve the default in open() z_max_page_size: ?u64, -hash_style: HashStyle, -compress_debug_sections: CompressDebugSections, -symbol_wrap_set: std.StringArrayHashMapUnmanaged(void), -sort_section: ?SortSection, soname: ?[]const u8, -bind_global_refs_locally: bool, -linker_script: ?[]const u8, -version_script: ?[]const u8, -allow_undefined_version: bool, -enable_new_dtags: ?bool, -print_icf_sections: bool, -print_map: bool, entry_name: ?[]const u8, ptr_width: PtrWidth, @@ -201,9 +189,6 @@ const minimum_atom_size = 64; pub const min_text_capacity = padToIdeal(minimum_atom_size); pub const PtrWidth = enum { p32, p64 }; -pub const HashStyle = enum { sysv, gnu, both }; -pub const CompressDebugSections = enum { none, zlib, zstd }; -pub const SortSection = enum { name, alignment }; pub fn createEmpty( arena: Allocator, @@ -214,7 +199,6 @@ pub fn createEmpty( const target = comp.root_mod.resolved_target.result; assert(target.ofmt == .elf); - const use_lld = build_options.have_llvm and comp.config.use_lld; const use_llvm = comp.config.use_llvm; const opt_zcu = comp.zcu; const output_mode = comp.config.output_mode; @@ -265,12 +249,10 @@ pub fn createEmpty( const is_dyn_lib = output_mode == .Lib and link_mode == .dynamic; const default_sym_version: elf.Versym = if (is_dyn_lib or comp.config.rdynamic) .GLOBAL else .LOCAL; - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. // If using LLVM to generate the object file for the zig compilation unit, // we need a place to put the object file so that it can be subsequently // handled. - const zcu_object_sub_path = if (!use_lld and !use_llvm) + const zcu_object_sub_path = if (!use_llvm) null else try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); @@ -292,7 +274,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .zig_object = null, @@ -317,7 +298,6 @@ pub fn createEmpty( }; }, - .emit_relocs = options.emit_relocs, .z_nodelete = options.z_nodelete, .z_notext = options.z_notext, .z_defs = options.z_defs, @@ -327,27 +307,11 @@ pub fn createEmpty( .z_relro = options.z_relro, .z_common_page_size = options.z_common_page_size, .z_max_page_size = options.z_max_page_size, - .hash_style = options.hash_style, - .compress_debug_sections = options.compress_debug_sections, - .symbol_wrap_set = options.symbol_wrap_set, - .sort_section = options.sort_section, .soname = options.soname, - .bind_global_refs_locally = options.bind_global_refs_locally, - .linker_script = options.linker_script, - .version_script = options.version_script, - .allow_undefined_version = options.allow_undefined_version, - .enable_new_dtags = options.enable_new_dtags, - .print_icf_sections = options.print_icf_sections, - .print_map = options.print_map, .dump_argv_list = .empty, }; errdefer self.base.destroy(); - if (use_lld and (use_llvm or !comp.config.have_zcu)) { - // LLVM emits the object file (if any); LLD links it into the final product. - return self; - } - // --verbose-link if (comp.verbose_link) try dumpArgvInit(self, arena); @@ -355,13 +319,11 @@ pub fn createEmpty( const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static); // What path should this ELF linker code output to? - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. - const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; + const sub_path = emit.sub_path; self.base.file = try emit.root_dir.handle.createFile(sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(use_lld, output_mode, link_mode), + .mode = link.File.determineMode(output_mode, link_mode), }); const gpa = comp.gpa; @@ -785,20 +747,6 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { } pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - const comp = self.base.comp; - const use_lld = build_options.have_llvm and comp.config.use_lld; - const diags = &comp.link_diags; - if (use_lld) { - return self.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), - }; - } - try self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -810,14 +758,14 @@ pub fn flushZcu(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: const sub_prog_node = prog_node.start("ELF Flush", 0); defer sub_prog_node.end(); - return flushZcuInner(self, arena, tid) catch |err| switch (err) { + return flushInner(self, arena, tid) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("ELF flush failed: {s}", .{@errorName(e)}), }; } -fn flushZcuInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { +fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { const comp = self.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -1492,643 +1440,6 @@ pub fn initOutputSection(self: *Elf, args: struct { return out_shndx; } -fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { - dev.check(.lld_linker); - - const tracy = trace(@src()); - defer tracy.end(); - - const comp = self.base.comp; - const gpa = comp.gpa; - const diags = &comp.link_diags; - - const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { - if (zcu.llvm_object == null) { - try self.flushZcu(arena, tid, prog_node); - } else { - // `Compilation.flush` has already made LLVM emit this object file for us. - } - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, self.base.zcu_object_sub_path.? }); - } else { - break :blk self.base.zcu_object_sub_path.?; - } - } else null; - - const sub_prog_node = prog_node.start("LLD Link", 0); - defer sub_prog_node.end(); - - const output_mode = comp.config.output_mode; - const is_obj = output_mode == .Obj; - const is_lib = output_mode == .Lib; - const link_mode = comp.config.link_mode; - const is_dyn_lib = link_mode == .dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe; - const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib; - const target = self.getTarget(); - const compiler_rt_path: ?Path = blk: { - if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; - if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - const ubsan_rt_path: ?Path = blk: { - if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; - if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - - // Here we want to determine whether we can save time by not invoking LLD when the - // output is unchanged. None of the linker options or the object files that are being - // linked are in the hash that namespaces the directory we are outputting to. Therefore, - // we must hash those now, and the resulting digest will form the "id" of the linking - // job we are about to perform. - // After a successful link, we store the id in the metadata of a symlink named "lld.id" in - // the artifact directory. So, now, we check if this symlink exists, and if it matches - // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD. - const id_symlink_basename = "lld.id"; - - var man: std.Build.Cache.Manifest = undefined; - defer if (!self.base.disable_lld_caching) man.deinit(); - - var digest: [std.Build.Cache.hex_digest_len]u8 = undefined; - - if (!self.base.disable_lld_caching) { - man = comp.cache_parent.obtain(); - - // We are about to obtain this lock, so here we give other processes a chance first. - self.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 14); - - try man.addOptionalFile(self.linker_script); - try man.addOptionalFile(self.version_script); - man.hash.add(self.allow_undefined_version); - man.hash.addOptional(self.enable_new_dtags); - try link.hashInputs(&man, comp.link_inputs); - for (comp.c_object_table.keys()) |key| { - _ = try man.addFilePath(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - try man.addOptionalFilePath(compiler_rt_path); - try man.addOptionalFilePath(ubsan_rt_path); - try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); - try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); - - // We can skip hashing libc and libc++ components that we are in charge of building from Zig - // installation sources because they are always a product of the compiler version + target information. - man.hash.addOptionalBytes(self.entry_name); - man.hash.add(self.image_base); - man.hash.add(self.base.gc_sections); - man.hash.addOptional(self.sort_section); - man.hash.add(comp.link_eh_frame_hdr); - man.hash.add(self.emit_relocs); - man.hash.add(comp.config.rdynamic); - man.hash.addListOfBytes(self.rpath_table.keys()); - if (output_mode == .Exe) { - man.hash.add(self.base.stack_size); - } - man.hash.add(self.base.build_id); - man.hash.addListOfBytes(self.symbol_wrap_set.keys()); - man.hash.add(comp.skip_linker_dependencies); - man.hash.add(self.z_nodelete); - man.hash.add(self.z_notext); - man.hash.add(self.z_defs); - man.hash.add(self.z_origin); - man.hash.add(self.z_nocopyreloc); - man.hash.add(self.z_now); - man.hash.add(self.z_relro); - man.hash.add(self.z_common_page_size orelse 0); - man.hash.add(self.z_max_page_size orelse 0); - man.hash.add(self.hash_style); - // strip does not need to go into the linker hash because it is part of the hash namespace - if (comp.config.link_libc) { - man.hash.add(comp.libc_installation != null); - if (comp.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - } - } - if (have_dynamic_linker) { - man.hash.addOptionalBytes(target.dynamic_linker.get()); - } - man.hash.addOptionalBytes(self.soname); - man.hash.addOptional(comp.version); - man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); - man.hash.add(self.base.allow_shlib_undefined); - man.hash.add(self.bind_global_refs_locally); - man.hash.add(self.compress_debug_sections); - man.hash.add(comp.config.any_sanitize_thread); - man.hash.add(comp.config.any_fuzz); - man.hash.addOptionalBytes(comp.sysroot); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = std.Build.Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("ELF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("ELF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - self.base.lock = man.toOwnedLock(); - return; - } - log.debug("ELF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - // Due to a deficiency in LLD, we need to special-case BPF to a simple file - // copy when generating relocatables. Normally, we would expect `lld -r` to work. - // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails - // before even generating the relocatable. - // - // For m68k, we go through this path because LLD doesn't support it yet, but LLVM can - // produce usable object files. - if (output_mode == .Obj and - (comp.config.lto != .none or - target.cpu.arch.isBpf() or - target.cpu.arch == .lanai or - target.cpu.arch == .m68k or - target.cpu.arch.isSPARC() or - target.cpu.arch == .ve or - target.cpu.arch == .xcore)) - { - // In this case we must do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk Path.initCwd(p); - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - try std.fs.Dir.copyFile( - the_object_path.root_dir.handle, - the_object_path.sub_path, - directory.handle, - self.base.emit.sub_path, - .{}, - ); - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(gpa); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - const linker_command = "ld.lld"; - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); - if (is_obj) { - try argv.append("-r"); - } - - try argv.append("--error-limit=0"); - - if (comp.sysroot) |sysroot| { - try argv.append(try std.fmt.allocPrint(arena, "--sysroot={s}", .{sysroot})); - } - - if (target_util.llvmMachineAbi(target)) |mabi| { - try argv.appendSlice(&.{ - "-mllvm", - try std.fmt.allocPrint(arena, "-target-abi={s}", .{mabi}), - }); - } - - try argv.appendSlice(&.{ - "-mllvm", - try std.fmt.allocPrint(arena, "-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}), - }); - - if (comp.config.lto != .none) { - switch (comp.root_mod.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("--lto-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"), - } - } - switch (comp.root_mod.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), - } - - if (self.entry_name) |name| { - try argv.appendSlice(&.{ "--entry", name }); - } - - for (comp.force_undefined_symbols.keys()) |sym| { - try argv.append("-u"); - try argv.append(sym); - } - - switch (self.hash_style) { - .gnu => try argv.append("--hash-style=gnu"), - .sysv => try argv.append("--hash-style=sysv"), - .both => {}, // this is the default - } - - if (output_mode == .Exe) { - try argv.appendSlice(&.{ - "-z", - try std.fmt.allocPrint(arena, "stack-size={d}", .{self.base.stack_size}), - }); - } - - switch (self.base.build_id) { - .none => try argv.append("--build-id=none"), - .fast, .uuid, .sha1, .md5 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ - @tagName(self.base.build_id), - })), - .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ - std.fmt.fmtSliceHexLower(hs.toSlice()), - })), - } - - try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{self.image_base})); - - if (self.linker_script) |linker_script| { - try argv.append("-T"); - try argv.append(linker_script); - } - - if (self.sort_section) |how| { - const arg = try std.fmt.allocPrint(arena, "--sort-section={s}", .{@tagName(how)}); - try argv.append(arg); - } - - if (self.base.gc_sections) { - try argv.append("--gc-sections"); - } - - if (self.base.print_gc_sections) { - try argv.append("--print-gc-sections"); - } - - if (self.print_icf_sections) { - try argv.append("--print-icf-sections"); - } - - if (self.print_map) { - try argv.append("--print-map"); - } - - if (comp.link_eh_frame_hdr) { - try argv.append("--eh-frame-hdr"); - } - - if (self.emit_relocs) { - try argv.append("--emit-relocs"); - } - - if (comp.config.rdynamic) { - try argv.append("--export-dynamic"); - } - - if (comp.config.debug_format == .strip) { - try argv.append("-s"); - } - - if (self.z_nodelete) { - try argv.append("-z"); - try argv.append("nodelete"); - } - if (self.z_notext) { - try argv.append("-z"); - try argv.append("notext"); - } - if (self.z_defs) { - try argv.append("-z"); - try argv.append("defs"); - } - if (self.z_origin) { - try argv.append("-z"); - try argv.append("origin"); - } - if (self.z_nocopyreloc) { - try argv.append("-z"); - try argv.append("nocopyreloc"); - } - if (self.z_now) { - // LLD defaults to -zlazy - try argv.append("-znow"); - } - if (!self.z_relro) { - // LLD defaults to -zrelro - try argv.append("-znorelro"); - } - if (self.z_common_page_size) |size| { - try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "common-page-size={d}", .{size})); - } - if (self.z_max_page_size) |size| { - try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "max-page-size={d}", .{size})); - } - - if (getLDMOption(target)) |ldm| { - try argv.append("-m"); - try argv.append(ldm); - } - - if (link_mode == .static) { - if (target.cpu.arch.isArm()) { - try argv.append("-Bstatic"); - } else { - try argv.append("-static"); - } - } else if (switch (target.os.tag) { - else => is_dyn_lib, - .haiku => is_exe_or_dyn_lib, - }) { - try argv.append("-shared"); - } - - if (comp.config.pie and output_mode == .Exe) { - try argv.append("-pie"); - } - - if (is_exe_or_dyn_lib and target.os.tag == .netbsd) { - // Add options to produce shared objects with only 2 PT_LOAD segments. - // NetBSD expects 2 PT_LOAD segments in a shared object, otherwise - // ld.elf_so fails loading dynamic libraries with "not found" error. - // See https://github.com/ziglang/zig/issues/9109 . - try argv.append("--no-rosegment"); - try argv.append("-znorelro"); - } - - try argv.append("-o"); - try argv.append(full_out_path); - - // csu prelude - const csu = try comp.getCrtPaths(arena); - if (csu.crt0) |p| try argv.append(try p.toString(arena)); - if (csu.crti) |p| try argv.append(try p.toString(arena)); - if (csu.crtbegin) |p| try argv.append(try p.toString(arena)); - - for (self.rpath_table.keys()) |rpath| { - try argv.appendSlice(&.{ "-rpath", rpath }); - } - - for (self.symbol_wrap_set.keys()) |symbol_name| { - try argv.appendSlice(&.{ "-wrap", symbol_name }); - } - - if (comp.config.link_libc) { - if (comp.libc_installation) |libc_installation| { - try argv.append("-L"); - try argv.append(libc_installation.crt_dir.?); - } - } - - if (have_dynamic_linker and - (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker)) - { - if (target.dynamic_linker.get()) |dynamic_linker| { - try argv.append("-dynamic-linker"); - try argv.append(dynamic_linker); - } - } - - if (is_dyn_lib) { - if (self.soname) |soname| { - try argv.append("-soname"); - try argv.append(soname); - } - if (self.version_script) |version_script| { - try argv.append("-version-script"); - try argv.append(version_script); - } - if (self.allow_undefined_version) { - try argv.append("--undefined-version"); - } else { - try argv.append("--no-undefined-version"); - } - if (self.enable_new_dtags) |enable_new_dtags| { - if (enable_new_dtags) { - try argv.append("--enable-new-dtags"); - } else { - try argv.append("--disable-new-dtags"); - } - } - } - - // Positional arguments to the linker such as object files. - var whole_archive = false; - - for (self.base.comp.link_inputs) |link_input| switch (link_input) { - .res => unreachable, // Windows-only - .dso => continue, - .object, .archive => |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - try argv.append(try obj.path.toString(arena)); - }, - .dso_exact => |dso_exact| { - assert(dso_exact.name[0] == ':'); - try argv.appendSlice(&.{ "-l", dso_exact.name }); - }, - }; - - if (whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(try key.status.success.object_path.toString(arena)); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - if (comp.tsan_lib) |lib| { - assert(comp.config.any_sanitize_thread); - try argv.append(try lib.full_object_path.toString(arena)); - } - - if (comp.fuzzer_lib) |lib| { - assert(comp.config.any_fuzz); - try argv.append(try lib.full_object_path.toString(arena)); - } - - if (ubsan_rt_path) |p| { - try argv.append(try p.toString(arena)); - } - - // Shared libraries. - if (is_exe_or_dyn_lib) { - // Worst-case, we need an --as-needed argument for every lib, as well - // as one before and one after. - try argv.ensureUnusedCapacity(2 * self.base.comp.link_inputs.len + 2); - argv.appendAssumeCapacity("--as-needed"); - var as_needed = true; - - for (self.base.comp.link_inputs) |link_input| switch (link_input) { - .res => unreachable, // Windows-only - .object, .archive, .dso_exact => continue, - .dso => |dso| { - const lib_as_needed = !dso.needed; - switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { - 0b00, 0b11 => {}, - 0b01 => { - argv.appendAssumeCapacity("--no-as-needed"); - as_needed = false; - }, - 0b10 => { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - }, - } - - // By this time, we depend on these libs being dynamically linked - // libraries and not static libraries (the check for that needs to be earlier), - // but they could be full paths to .so files, in which case we - // want to avoid prepending "-l". - argv.appendAssumeCapacity(try dso.path.toString(arena)); - }, - }; - - if (!as_needed) { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - } - - // libc++ dep - if (comp.config.link_libcpp) { - try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); - try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); - } - - // libunwind dep - if (comp.config.link_libunwind) { - try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); - } - - // libc dep - diags.flags.missing_libc = false; - if (comp.config.link_libc) { - if (comp.libc_installation != null) { - const needs_grouping = link_mode == .static; - if (needs_grouping) try argv.append("--start-group"); - try argv.appendSlice(target_util.libcFullLinkFlags(target)); - if (needs_grouping) try argv.append("--end-group"); - } else if (target.isGnuLibC()) { - for (glibc.libs) |lib| { - if (lib.removed_in) |rem_in| { - if (target.os.versionRange().gnuLibCVersion().?.order(rem_in) != .lt) continue; - } - - const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ - comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); - } - try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a")); - } else if (target.isMuslLibC()) { - try argv.append(try comp.crtFileAsString(arena, switch (link_mode) { - .static => "libc.a", - .dynamic => "libc.so", - })); - } else if (target.isFreeBSDLibC()) { - for (freebsd.libs) |lib| { - const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ - comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); - } - } else if (target.isNetBSDLibC()) { - for (netbsd.libs) |lib| { - const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ - comp.netbsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); - } - } else { - diags.flags.missing_libc = true; - } - - if (comp.zigc_static_lib) |zigc| { - try argv.append(try zigc.full_object_path.toString(arena)); - } - } - } - - // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs - // to be after the shared libraries, so they are picked up from the shared - // libraries, not libcompiler_rt. - if (compiler_rt_path) |p| { - try argv.append(try p.toString(arena)); - } - - // crt postlude - if (csu.crtend) |p| try argv.append(try p.toString(arena)); - if (csu.crtn) |p| try argv.append(try p.toString(arena)); - - if (self.base.allow_shlib_undefined) { - try argv.append("--allow-shlib-undefined"); - } - - switch (self.compress_debug_sections) { - .none => {}, - .zlib => try argv.append("--compress-debug-sections=zlib"), - .zstd => try argv.append("--compress-debug-sections=zstd"), - } - - if (self.bind_global_refs_locally) { - try argv.append("-Bsymbolic"); - } - - try link.spawnLld(comp, arena, argv.items); - } - - if (!self.base.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - std.Build.Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - self.base.lock = man.toOwnedLock(); - } -} - pub fn writeShdrTable(self: *Elf) !void { const gpa = self.base.comp.gpa; const target_endian = self.getTarget().cpu.arch.endian(); @@ -4121,85 +3432,6 @@ fn shdrTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { }; } -fn getLDMOption(target: std.Target) ?[]const u8 { - // This should only return emulations understood by LLD's parseEmulation(). - return switch (target.cpu.arch) { - .aarch64 => switch (target.os.tag) { - .linux => "aarch64linux", - else => "aarch64elf", - }, - .aarch64_be => switch (target.os.tag) { - .linux => "aarch64linuxb", - else => "aarch64elfb", - }, - .amdgcn => "elf64_amdgpu", - .arm, .thumb => switch (target.os.tag) { - .linux => "armelf_linux_eabi", - else => "armelf", - }, - .armeb, .thumbeb => switch (target.os.tag) { - .linux => "armelfb_linux_eabi", - else => "armelfb", - }, - .hexagon => "hexagonelf", - .loongarch32 => "elf32loongarch", - .loongarch64 => "elf64loongarch", - .mips => switch (target.os.tag) { - .freebsd => "elf32btsmip_fbsd", - else => "elf32btsmip", - }, - .mipsel => switch (target.os.tag) { - .freebsd => "elf32ltsmip_fbsd", - else => "elf32ltsmip", - }, - .mips64 => switch (target.os.tag) { - .freebsd => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32btsmipn32_fbsd", - else => "elf64btsmip_fbsd", - }, - else => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32btsmipn32", - else => "elf64btsmip", - }, - }, - .mips64el => switch (target.os.tag) { - .freebsd => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32ltsmipn32_fbsd", - else => "elf64ltsmip_fbsd", - }, - else => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32ltsmipn32", - else => "elf64ltsmip", - }, - }, - .msp430 => "msp430elf", - .powerpc => switch (target.os.tag) { - .freebsd => "elf32ppc_fbsd", - .linux => "elf32ppclinux", - else => "elf32ppc", - }, - .powerpcle => switch (target.os.tag) { - .linux => "elf32lppclinux", - else => "elf32lppc", - }, - .powerpc64 => "elf64ppc", - .powerpc64le => "elf64lppc", - .riscv32 => "elf32lriscv", - .riscv64 => "elf64lriscv", - .s390x => "elf64_s390", - .sparc64 => "elf64_sparc", - .x86 => switch (target.os.tag) { - .freebsd => "elf_i386_fbsd", - else => "elf_i386", - }, - .x86_64 => switch (target.abi) { - .gnux32, .muslx32 => "elf32_x86_64", - else => "elf_x86_64", - }, - else => null, - }; -} - pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { return actual_size +| (actual_size / ideal_factor); } @@ -5284,10 +4516,7 @@ const codegen = @import("../codegen.zig"); const dev = @import("../dev.zig"); const eh_frame = @import("Elf/eh_frame.zig"); const gc = @import("Elf/gc.zig"); -const glibc = @import("../libs/glibc.zig"); const musl = @import("../libs/musl.zig"); -const freebsd = @import("../libs/freebsd.zig"); -const netbsd = @import("../libs/netbsd.zig"); const link = @import("../link.zig"); const relocatable = @import("Elf/relocatable.zig"); const relocation = @import("Elf/relocation.zig"); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 49921089f7..e377f3a9af 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -310,7 +310,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { if (self.dwarf) |*dwarf| { const pt: Zcu.PerThread = .activate(elf_file.base.comp.zcu.?, tid); defer pt.deactivate(); - try dwarf.flushZcu(pt); + try dwarf.flush(pt); const gpa = elf_file.base.comp.gpa; const cpu_arch = elf_file.getTarget().cpu.arch; @@ -481,7 +481,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { self.debug_str_section_dirty = false; } - // The point of flushZcu() is to commit changes, so in theory, nothing should + // The point of flush() is to commit changes, so in theory, nothing should // be dirty after this. However, it is possible for some things to remain // dirty because they fail to be written in the event of compile errors, // such as debug_line_header_dirty and debug_info_header_dirty. @@ -661,7 +661,7 @@ pub fn scanRelocs(self: *ZigObject, elf_file: *Elf, undefs: anytype) !void { if (shdr.sh_type == elf.SHT_NOBITS) continue; if (atom_ptr.scanRelocsRequiresCode(elf_file)) { // TODO ideally we don't have to fetch the code here. - // Perhaps it would make sense to save the code until flushZcu where we + // Perhaps it would make sense to save the code until flush where we // would free all of generated code? const code = try self.codeAlloc(elf_file, atom_index); defer gpa.free(code); @@ -1075,7 +1075,7 @@ pub fn getOrCreateMetadataForLazySymbol( } state_ptr.* = .pending_flush; const symbol_index = symbol_index_ptr.*; - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(elf_file, pt, lazy_sym, symbol_index); return symbol_index; } diff --git a/src/link/Goff.zig b/src/link/Goff.zig index 35821289cd..28da184495 100644 --- a/src/link/Goff.zig +++ b/src/link/Goff.zig @@ -46,7 +46,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 0, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, }; @@ -105,10 +104,6 @@ pub fn updateExports( } pub fn flush(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = self; _ = arena; _ = tid; diff --git a/src/link/Lld.zig b/src/link/Lld.zig new file mode 100644 index 0000000000..ba52d0c8d4 --- /dev/null +++ b/src/link/Lld.zig @@ -0,0 +1,2148 @@ +base: link.File, +disable_caching: bool, +ofmt: union(enum) { + elf: Elf, + coff: Coff, + wasm: Wasm, +}, + +const Coff = struct { + image_base: u64, + entry: link.File.OpenOptions.Entry, + pdb_out_path: ?[]const u8, + repro: bool, + tsaware: bool, + nxcompat: bool, + dynamicbase: bool, + /// TODO this and minor_subsystem_version should be combined into one property and left as + /// default or populated together. They should not be separate fields. + major_subsystem_version: u16, + minor_subsystem_version: u16, + lib_directories: []const Cache.Directory, + module_definition_file: ?[]const u8, + subsystem: ?std.Target.SubSystem, + /// These flags are populated by `codegen.llvm.updateExports` to allow us to guess the subsystem. + lld_export_flags: struct { + c_main: bool, + winmain: bool, + wwinmain: bool, + winmain_crt_startup: bool, + wwinmain_crt_startup: bool, + dllmain_crt_startup: bool, + }, + fn init(comp: *Compilation, options: link.File.OpenOptions) !Coff { + const target = comp.root_mod.resolved_target.result; + const output_mode = comp.config.output_mode; + return .{ + .image_base = options.image_base orelse switch (output_mode) { + .Exe => switch (target.cpu.arch) { + .aarch64, .x86_64 => 0x140000000, + .thumb, .x86 => 0x400000, + else => unreachable, + }, + .Lib => switch (target.cpu.arch) { + .aarch64, .x86_64 => 0x180000000, + .thumb, .x86 => 0x10000000, + else => unreachable, + }, + .Obj => 0, + }, + .entry = options.entry, + .pdb_out_path = options.pdb_out_path, + .repro = options.repro, + .tsaware = options.tsaware, + .nxcompat = options.nxcompat, + .dynamicbase = options.dynamicbase, + .major_subsystem_version = options.major_subsystem_version orelse 6, + .minor_subsystem_version = options.minor_subsystem_version orelse 0, + .lib_directories = options.lib_directories, + .module_definition_file = options.module_definition_file, + // Subsystem depends on the set of public symbol names from linked objects. + // See LinkerDriver::inferSubsystem from the LLD project for the flow chart. + .subsystem = options.subsystem, + // These flags are initially all `false`; the LLVM backend populates them when it learns about exports. + .lld_export_flags = .{ + .c_main = false, + .winmain = false, + .wwinmain = false, + .winmain_crt_startup = false, + .wwinmain_crt_startup = false, + .dllmain_crt_startup = false, + }, + }; + } +}; +pub const Elf = struct { + entry_name: ?[]const u8, + hash_style: HashStyle, + image_base: u64, + linker_script: ?[]const u8, + version_script: ?[]const u8, + sort_section: ?SortSection, + print_icf_sections: bool, + print_map: bool, + emit_relocs: bool, + z_nodelete: bool, + z_notext: bool, + z_defs: bool, + z_origin: bool, + z_nocopyreloc: bool, + z_now: bool, + z_relro: bool, + z_common_page_size: ?u64, + z_max_page_size: ?u64, + rpath_list: []const []const u8, + symbol_wrap_set: []const []const u8, + soname: ?[]const u8, + allow_undefined_version: bool, + enable_new_dtags: ?bool, + compress_debug_sections: CompressDebugSections, + bind_global_refs_locally: bool, + pub const HashStyle = enum { sysv, gnu, both }; + pub const SortSection = enum { name, alignment }; + pub const CompressDebugSections = enum { none, zlib, zstd }; + + fn init(comp: *Compilation, options: link.File.OpenOptions) !Elf { + const PtrWidth = enum { p32, p64 }; + const target = comp.root_mod.resolved_target.result; + const output_mode = comp.config.output_mode; + const is_dyn_lib = output_mode == .Lib and comp.config.link_mode == .dynamic; + const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { + 0...32 => .p32, + 33...64 => .p64, + else => return error.UnsupportedElfArchitecture, + }; + const default_entry_name: []const u8 = switch (target.cpu.arch) { + .mips, .mipsel, .mips64, .mips64el => "__start", + else => "_start", + }; + return .{ + .entry_name = switch (options.entry) { + .disabled => null, + .default => if (output_mode != .Exe) null else default_entry_name, + .enabled => default_entry_name, + .named => |name| name, + }, + .hash_style = options.hash_style, + .image_base = b: { + if (is_dyn_lib) break :b 0; + if (output_mode == .Exe and comp.config.pie) break :b 0; + break :b options.image_base orelse switch (ptr_width) { + .p32 => 0x10000, + .p64 => 0x1000000, + }; + }, + .linker_script = options.linker_script, + .version_script = options.version_script, + .sort_section = options.sort_section, + .print_icf_sections = options.print_icf_sections, + .print_map = options.print_map, + .emit_relocs = options.emit_relocs, + .z_nodelete = options.z_nodelete, + .z_notext = options.z_notext, + .z_defs = options.z_defs, + .z_origin = options.z_origin, + .z_nocopyreloc = options.z_nocopyreloc, + .z_now = options.z_now, + .z_relro = options.z_relro, + .z_common_page_size = options.z_common_page_size, + .z_max_page_size = options.z_max_page_size, + .rpath_list = options.rpath_list, + .symbol_wrap_set = options.symbol_wrap_set.keys(), + .soname = options.soname, + .allow_undefined_version = options.allow_undefined_version, + .enable_new_dtags = options.enable_new_dtags, + .compress_debug_sections = options.compress_debug_sections, + .bind_global_refs_locally = options.bind_global_refs_locally, + }; + } +}; +const Wasm = struct { + /// Symbol name of the entry function to export + entry_name: ?[]const u8, + /// When true, will import the function table from the host environment. + import_table: bool, + /// When true, will export the function table to the host environment. + export_table: bool, + /// When defined, sets the initial memory size of the memory. + initial_memory: ?u64, + /// When defined, sets the maximum memory size of the memory. + max_memory: ?u64, + /// When defined, sets the start of the data section. + global_base: ?u64, + /// Set of *global* symbol names to export to the host environment. + export_symbol_names: []const []const u8, + /// When true, will allow undefined symbols + import_symbols: bool, + fn init(comp: *Compilation, options: link.File.OpenOptions) !Wasm { + const default_entry_name: []const u8 = switch (comp.config.wasi_exec_model) { + .reactor => "_initialize", + .command => "_start", + }; + return .{ + .entry_name = switch (options.entry) { + .disabled => null, + .default => if (comp.config.output_mode != .Exe) null else default_entry_name, + .enabled => default_entry_name, + .named => |name| name, + }, + .import_table = options.import_table, + .export_table = options.export_table, + .initial_memory = options.initial_memory, + .max_memory = options.max_memory, + .global_base = options.global_base, + .export_symbol_names = options.export_symbol_names, + .import_symbols = options.import_symbols, + }; + } +}; + +pub fn createEmpty( + arena: Allocator, + comp: *Compilation, + emit: Cache.Path, + options: link.File.OpenOptions, +) !*Lld { + const target = comp.root_mod.resolved_target.result; + const output_mode = comp.config.output_mode; + const optimize_mode = comp.root_mod.optimize_mode; + const is_native_os = comp.root_mod.resolved_target.is_native_os; + + const obj_file_ext: []const u8 = switch (target.ofmt) { + .coff => "obj", + .elf, .wasm => "o", + else => unreachable, + }; + const gc_sections: bool = options.gc_sections orelse switch (target.ofmt) { + .coff => optimize_mode != .Debug, + .elf => optimize_mode != .Debug and output_mode != .Obj, + .wasm => output_mode != .Obj, + else => unreachable, + }; + const stack_size: u64 = options.stack_size orelse default: { + if (target.ofmt == .wasm and target.os.tag == .freestanding) + break :default 1 * 1024 * 1024; // 1 MiB + break :default 16 * 1024 * 1024; // 16 MiB + }; + + const lld = try arena.create(Lld); + lld.* = .{ + .base = .{ + .tag = .lld, + .comp = comp, + .emit = emit, + .zcu_object_sub_path = try allocPrint(arena, "{s}.{s}", .{ emit.sub_path, obj_file_ext }), + .gc_sections = gc_sections, + .print_gc_sections = options.print_gc_sections, + .stack_size = stack_size, + .allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os, + .file = null, + .build_id = options.build_id, + }, + .disable_caching = options.disable_lld_caching, + .ofmt = switch (target.ofmt) { + .coff => .{ .coff = try .init(comp, options) }, + .elf => .{ .elf = try .init(comp, options) }, + .wasm => .{ .wasm = try .init(comp, options) }, + else => unreachable, + }, + }; + return lld; +} +pub fn deinit(lld: *Lld) void { + _ = lld; +} +pub fn flush( + lld: *Lld, + arena: Allocator, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) link.File.FlushError!void { + dev.check(.lld_linker); + _ = tid; + + const tracy = trace(@src()); + defer tracy.end(); + + const sub_prog_node = prog_node.start("LLD Link", 0); + defer sub_prog_node.end(); + + const comp = lld.base.comp; + const result = if (comp.config.output_mode == .Lib and comp.config.link_mode == .static) r: { + break :r linkAsArchive(lld, arena); + } else switch (lld.ofmt) { + .coff => coffLink(lld, arena), + .elf => elfLink(lld, arena), + .wasm => wasmLink(lld, arena), + }; + result catch |err| switch (err) { + error.OutOfMemory, error.LinkFailure => |e| return e, + else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), + }; +} + +fn linkAsArchive(lld: *Lld, arena: Allocator) !void { + const base = &lld.base; + const comp = base.comp; + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + const full_out_path_z = try arena.dupeZ(u8, full_out_path); + const opt_zcu = comp.zcu; + + // If there is no Zig code to compile, then we should skip flushing the output file + // because it will not be part of the linker line anyway. + const zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: { + const dirname = fs.path.dirname(full_out_path_z) orelse "."; + break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else null; + + log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"}); + + const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj) + comp.compiler_rt_obj.?.full_object_path + else + null; + + const ubsan_rt_path: ?Cache.Path = if (comp.ubsan_rt_strat == .obj) + comp.ubsan_rt_obj.?.full_object_path + else + null; + + // This function follows the same pattern as link.Elf.linkWithLLD so if you want some + // insight as to what's going on here you can read that function body which is more + // well-commented. + + const id_symlink_basename = "llvm-ar.id"; + + var man: Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + const link_inputs = comp.link_inputs; + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + base.releaseLock(); + + try link.hashInputs(&man, link_inputs); + + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + for (comp.win32_resource_table.keys()) |key| { + _ = try man.addFile(key.status.success.res_path, null); + } + try man.addOptionalFile(zcu_obj_path); + try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| b: { + log.debug("archive new_digest={s} readFile error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + break :b prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("archive digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + base.lock = man.toOwnedLock(); + return; + } + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + var object_files: std.ArrayListUnmanaged([*:0]const u8) = .empty; + + try object_files.ensureUnusedCapacity(arena, link_inputs.len); + for (link_inputs) |input| { + object_files.appendAssumeCapacity(try input.path().?.toStringZ(arena)); + } + + try object_files.ensureUnusedCapacity(arena, comp.c_object_table.count() + + comp.win32_resource_table.count() + 2); + + for (comp.c_object_table.keys()) |key| { + object_files.appendAssumeCapacity(try key.status.success.object_path.toStringZ(arena)); + } + for (comp.win32_resource_table.keys()) |key| { + object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path)); + } + if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); + if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); + if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); + + if (comp.verbose_link) { + std.debug.print("ar rcs {s}", .{full_out_path_z}); + for (object_files.items) |arg| { + std.debug.print(" {s}", .{arg}); + } + std.debug.print("\n", .{}); + } + + const llvm_bindings = @import("../codegen/llvm/bindings.zig"); + const llvm = @import("../codegen/llvm.zig"); + const target = comp.root_mod.resolved_target.result; + llvm.initializeLLVMTarget(target.cpu.arch); + const bad = llvm_bindings.WriteArchive( + full_out_path_z, + object_files.items.ptr, + object_files.items.len, + switch (target.os.tag) { + .aix => .AIXBIG, + .windows => .COFF, + else => if (target.os.tag.isDarwin()) .DARWIN else .GNU, + }, + ); + if (bad) return error.UnableToWriteArchive; + + if (!lld.disable_caching) { + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)}); + }; + + if (man.have_exclusive_lock) { + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)}); + }; + } + + base.lock = man.toOwnedLock(); + } +} + +fn coffLink(lld: *Lld, arena: Allocator) !void { + const comp = lld.base.comp; + const gpa = comp.gpa; + const base = &lld.base; + const coff = &lld.ofmt.coff; + + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else { + break :p base.zcu_object_sub_path.?; + } + } else null; + + const is_lib = comp.config.output_mode == .Lib; + const is_dyn_lib = comp.config.link_mode == .dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or comp.config.output_mode == .Exe; + const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib; + const target = comp.root_mod.resolved_target.result; + const optimize_mode = comp.root_mod.optimize_mode; + const entry_name: ?[]const u8 = switch (coff.entry) { + // This logic isn't quite right for disabled or enabled. No point in fixing it + // when the goal is to eliminate dependency on LLD anyway. + // https://github.com/ziglang/zig/issues/17751 + .disabled, .default, .enabled => null, + .named => |name| name, + }; + + // See link/Elf.zig for comments on how this mechanism works. + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 14); + + try link.hashInputs(&man, comp.link_inputs); + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + for (comp.win32_resource_table.keys()) |key| { + _ = try man.addFile(key.status.success.res_path, null); + } + try man.addOptionalFile(module_obj_path); + man.hash.addOptionalBytes(entry_name); + man.hash.add(base.stack_size); + man.hash.add(coff.image_base); + man.hash.add(base.build_id); + { + // TODO remove this, libraries must instead be resolved by the frontend. + for (coff.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path); + } + man.hash.add(comp.skip_linker_dependencies); + if (comp.config.link_libc) { + man.hash.add(comp.libc_installation != null); + if (comp.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + if (target.abi == .msvc or target.abi == .itanium) { + man.hash.addBytes(libc_installation.msvc_lib_dir.?); + man.hash.addBytes(libc_installation.kernel32_lib_dir.?); + } + } + } + man.hash.addListOfBytes(comp.windows_libs.keys()); + man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); + man.hash.addOptional(coff.subsystem); + man.hash.add(comp.config.is_test); + man.hash.add(coff.tsaware); + man.hash.add(coff.nxcompat); + man.hash.add(coff.dynamicbase); + man.hash.add(base.allow_shlib_undefined); + // strip does not need to go into the linker hash because it is part of the hash namespace + man.hash.add(coff.major_subsystem_version); + man.hash.add(coff.minor_subsystem_version); + man.hash.add(coff.repro); + man.hash.addOptional(comp.version); + try man.addOptionalFile(coff.module_definition_file); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + base.lock = man.toOwnedLock(); + return; + } + log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (comp.config.output_mode == .Obj) { + // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk Cache.Path.initCwd(p); + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + try std.fs.Dir.copyFile( + the_object_path.root_dir.handle, + the_object_path.sub_path, + directory.handle, + base.emit.sub_path, + .{}, + ); + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(gpa); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + const linker_command = "lld-link"; + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + + if (target.isMinGW()) { + try argv.append("-lldmingw"); + } + + try argv.append("-ERRORLIMIT:0"); + try argv.append("-NOLOGO"); + if (comp.config.debug_format != .strip) { + try argv.append("-DEBUG"); + + const out_ext = std.fs.path.extension(full_out_path); + const out_pdb = coff.pdb_out_path orelse try allocPrint(arena, "{s}.pdb", .{ + full_out_path[0 .. full_out_path.len - out_ext.len], + }); + const out_pdb_basename = std.fs.path.basename(out_pdb); + + try argv.append(try allocPrint(arena, "-PDB:{s}", .{out_pdb})); + try argv.append(try allocPrint(arena, "-PDBALTPATH:{s}", .{out_pdb_basename})); + } + if (comp.version) |version| { + try argv.append(try allocPrint(arena, "-VERSION:{}.{}", .{ version.major, version.minor })); + } + + if (target_util.llvmMachineAbi(target)) |mabi| { + try argv.append(try allocPrint(arena, "-MLLVM:-target-abi={s}", .{mabi})); + } + + try argv.append(try allocPrint(arena, "-MLLVM:-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"})); + + if (comp.config.lto != .none) { + switch (optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } + if (comp.config.output_mode == .Exe) { + try argv.append(try allocPrint(arena, "-STACK:{d}", .{base.stack_size})); + } + try argv.append(try allocPrint(arena, "-BASE:{d}", .{coff.image_base})); + + switch (base.build_id) { + .none => try argv.append("-BUILD-ID:NO"), + .fast => try argv.append("-BUILD-ID"), + .uuid, .sha1, .md5, .hexstring => {}, + } + + if (target.cpu.arch == .x86) { + try argv.append("-MACHINE:X86"); + } else if (target.cpu.arch == .x86_64) { + try argv.append("-MACHINE:X64"); + } else if (target.cpu.arch == .thumb) { + try argv.append("-MACHINE:ARM"); + } else if (target.cpu.arch == .aarch64) { + try argv.append("-MACHINE:ARM64"); + } + + for (comp.force_undefined_symbols.keys()) |symbol| { + try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); + } + + if (is_dyn_lib) { + try argv.append("-DLL"); + } + + if (entry_name) |name| { + try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{name})); + } + + if (coff.repro) { + try argv.append("-BREPRO"); + } + + if (coff.tsaware) { + try argv.append("-tsaware"); + } + if (coff.nxcompat) { + try argv.append("-nxcompat"); + } + if (!coff.dynamicbase) { + try argv.append("-dynamicbase:NO"); + } + if (base.allow_shlib_undefined) { + try argv.append("-FORCE:UNRESOLVED"); + } + + try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + + if (comp.implib_emit) |emit| { + const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + + if (comp.config.link_libc) { + if (comp.libc_installation) |libc_installation| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); + + if (target.abi == .msvc or target.abi == .itanium) { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); + } + } + } + + for (coff.lib_directories) |lib_directory| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_directory.path orelse "."})); + } + + try argv.ensureUnusedCapacity(comp.link_inputs.len); + for (comp.link_inputs) |link_input| switch (link_input) { + .dso_exact => unreachable, // not applicable to PE/COFF + inline .dso, .res => |x| { + argv.appendAssumeCapacity(try x.path.toString(arena)); + }, + .object, .archive => |obj| { + if (obj.must_link) { + argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Cache.Path, obj.path)})); + } else { + argv.appendAssumeCapacity(try obj.path.toString(arena)); + } + }, + }; + + for (comp.c_object_table.keys()) |key| { + try argv.append(try key.status.success.object_path.toString(arena)); + } + + for (comp.win32_resource_table.keys()) |key| { + try argv.append(key.status.success.res_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + if (coff.module_definition_file) |def| { + try argv.append(try allocPrint(arena, "-DEF:{s}", .{def})); + } + + const resolved_subsystem: ?std.Target.SubSystem = blk: { + if (coff.subsystem) |explicit| break :blk explicit; + switch (target.os.tag) { + .windows => { + if (comp.zcu != null) { + if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib) + break :blk null; + if (coff.lld_export_flags.c_main or comp.config.is_test or + coff.lld_export_flags.winmain_crt_startup or + coff.lld_export_flags.wwinmain_crt_startup) + { + break :blk .Console; + } + if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain) + break :blk .Windows; + } + }, + .uefi => break :blk .EfiApplication, + else => {}, + } + break :blk null; + }; + + const Mode = enum { uefi, win32 }; + const mode: Mode = mode: { + if (resolved_subsystem) |subsystem| { + const subsystem_suffix = try allocPrint(arena, ",{d}.{d}", .{ + coff.major_subsystem_version, coff.minor_subsystem_version, + }); + + switch (subsystem) { + .Console => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .EfiApplication => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiBootServiceDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRom => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRuntimeDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .Native => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Posix => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Windows => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + } + } else if (target.os.tag == .uefi) { + break :mode .uefi; + } else { + break :mode .win32; + } + }; + + switch (mode) { + .uefi => try argv.appendSlice(&[_][]const u8{ + "-BASE:0", + "-ENTRY:EfiMain", + "-OPT:REF", + "-SAFESEH:NO", + "-MERGE:.rdata=.data", + "-NODEFAULTLIB", + "-SECTION:.xdata,D", + }), + .win32 => { + if (link_in_crt) { + if (target.abi.isGnu()) { + if (target.cpu.arch == .x86) { + try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); + } else { + try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); + } + + if (is_dyn_lib) { + try argv.append(try comp.crtFileAsString(arena, "dllcrt2.obj")); + if (target.cpu.arch == .x86) { + try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); + } else { + try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); + } + } else { + try argv.append(try comp.crtFileAsString(arena, "crt2.obj")); + } + + try argv.append(try comp.crtFileAsString(arena, "libmingw32.lib")); + } else { + try argv.append(switch (comp.config.link_mode) { + .static => "libcmt.lib", + .dynamic => "msvcrt.lib", + }); + + const lib_str = switch (comp.config.link_mode) { + .static => "lib", + .dynamic => "", + }; + try argv.append(try allocPrint(arena, "{s}vcruntime.lib", .{lib_str})); + try argv.append(try allocPrint(arena, "{s}ucrt.lib", .{lib_str})); + + //Visual C++ 2015 Conformance Changes + //https://msdn.microsoft.com/en-us/library/bb531344.aspx + try argv.append("legacy_stdio_definitions.lib"); + + // msvcrt depends on kernel32 and ntdll + try argv.append("kernel32.lib"); + try argv.append("ntdll.lib"); + } + } else { + try argv.append("-NODEFAULTLIB"); + if (!is_lib and entry_name == null) { + if (comp.zcu != null) { + if (coff.lld_export_flags.winmain_crt_startup) { + try argv.append("-ENTRY:WinMainCRTStartup"); + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } + } + }, + } + + if (comp.config.link_libc and link_in_crt) { + if (comp.zigc_static_lib) |zigc| { + try argv.append(try zigc.full_object_path.toString(arena)); + } + } + + // libc++ dep + if (comp.config.link_libcpp) { + try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); + try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); + } + + // libunwind dep + if (comp.config.link_libunwind) { + try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); + } + + if (comp.config.any_fuzz) { + try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); + } + + const ubsan_rt_path: ?Cache.Path = blk: { + if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; + if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + if (ubsan_rt_path) |path| { + try argv.append(try path.toString(arena)); + } + + if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { + // MSVC compiler_rt is missing some stuff, so we build it unconditionally but + // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. + if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); + if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); + } + + try argv.ensureUnusedCapacity(comp.windows_libs.count()); + for (comp.windows_libs.keys()) |key| { + const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); + if (comp.crt_files.get(lib_basename)) |crt_file| { + argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); + continue; + } + if (try findLib(arena, lib_basename, coff.lib_directories)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + if (target.abi.isGnu()) { + const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); + if (try findLib(arena, fallback_name, coff.lib_directories)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + } + if (target.abi == .msvc or target.abi == .itanium) { + argv.appendAssumeCapacity(lib_basename); + continue; + } + + log.err("DLL import library for -l{s} not found", .{key}); + return error.DllImportLibraryNotFound; + } + + try spawnLld(comp, arena, argv.items); + } + + if (!lld.disable_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + base.lock = man.toOwnedLock(); + } +} +fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 { + for (lib_directories) |lib_directory| { + lib_directory.handle.access(name, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + return try lib_directory.join(arena, &.{name}); + } + return null; +} + +fn elfLink(lld: *Lld, arena: Allocator) !void { + const comp = lld.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const base = &lld.base; + const elf = &lld.ofmt.elf; + + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else { + break :p base.zcu_object_sub_path.?; + } + } else null; + + const output_mode = comp.config.output_mode; + const is_obj = output_mode == .Obj; + const is_lib = output_mode == .Lib; + const link_mode = comp.config.link_mode; + const is_dyn_lib = link_mode == .dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe; + const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib; + const target = comp.root_mod.resolved_target.result; + const compiler_rt_path: ?Cache.Path = blk: { + if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; + if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + const ubsan_rt_path: ?Cache.Path = blk: { + if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; + if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + + // Here we want to determine whether we can save time by not invoking LLD when the + // output is unchanged. None of the linker options or the object files that are being + // linked are in the hash that namespaces the directory we are outputting to. Therefore, + // we must hash those now, and the resulting digest will form the "id" of the linking + // job we are about to perform. + // After a successful link, we store the id in the metadata of a symlink named "lld.id" in + // the artifact directory. So, now, we check if this symlink exists, and if it matches + // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD. + const id_symlink_basename = "lld.id"; + + var man: std.Build.Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + var digest: [std.Build.Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 14); + + try man.addOptionalFile(elf.linker_script); + try man.addOptionalFile(elf.version_script); + man.hash.add(elf.allow_undefined_version); + man.hash.addOptional(elf.enable_new_dtags); + try link.hashInputs(&man, comp.link_inputs); + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); + try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); + try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); + + // We can skip hashing libc and libc++ components that we are in charge of building from Zig + // installation sources because they are always a product of the compiler version + target information. + man.hash.addOptionalBytes(elf.entry_name); + man.hash.add(elf.image_base); + man.hash.add(base.gc_sections); + man.hash.addOptional(elf.sort_section); + man.hash.add(comp.link_eh_frame_hdr); + man.hash.add(elf.emit_relocs); + man.hash.add(comp.config.rdynamic); + man.hash.addListOfBytes(elf.rpath_list); + if (output_mode == .Exe) { + man.hash.add(base.stack_size); + } + man.hash.add(base.build_id); + man.hash.addListOfBytes(elf.symbol_wrap_set); + man.hash.add(comp.skip_linker_dependencies); + man.hash.add(elf.z_nodelete); + man.hash.add(elf.z_notext); + man.hash.add(elf.z_defs); + man.hash.add(elf.z_origin); + man.hash.add(elf.z_nocopyreloc); + man.hash.add(elf.z_now); + man.hash.add(elf.z_relro); + man.hash.add(elf.z_common_page_size orelse 0); + man.hash.add(elf.z_max_page_size orelse 0); + man.hash.add(elf.hash_style); + // strip does not need to go into the linker hash because it is part of the hash namespace + if (comp.config.link_libc) { + man.hash.add(comp.libc_installation != null); + if (comp.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + } + } + if (have_dynamic_linker) { + man.hash.addOptionalBytes(target.dynamic_linker.get()); + } + man.hash.addOptionalBytes(elf.soname); + man.hash.addOptional(comp.version); + man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); + man.hash.add(base.allow_shlib_undefined); + man.hash.add(elf.bind_global_refs_locally); + man.hash.add(elf.compress_debug_sections); + man.hash.add(comp.config.any_sanitize_thread); + man.hash.add(comp.config.any_fuzz); + man.hash.addOptionalBytes(comp.sysroot); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = std.Build.Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("ELF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("ELF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + base.lock = man.toOwnedLock(); + return; + } + log.debug("ELF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + // Due to a deficiency in LLD, we need to special-case BPF to a simple file + // copy when generating relocatables. Normally, we would expect `lld -r` to work. + // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails + // before even generating the relocatable. + // + // For m68k, we go through this path because LLD doesn't support it yet, but LLVM can + // produce usable object files. + if (output_mode == .Obj and + (comp.config.lto != .none or + target.cpu.arch.isBpf() or + target.cpu.arch == .lanai or + target.cpu.arch == .m68k or + target.cpu.arch.isSPARC() or + target.cpu.arch == .ve or + target.cpu.arch == .xcore)) + { + // In this case we must do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk Cache.Path.initCwd(p); + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + try std.fs.Dir.copyFile( + the_object_path.root_dir.handle, + the_object_path.sub_path, + directory.handle, + base.emit.sub_path, + .{}, + ); + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(gpa); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + const linker_command = "ld.lld"; + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + if (is_obj) { + try argv.append("-r"); + } + + try argv.append("--error-limit=0"); + + if (comp.sysroot) |sysroot| { + try argv.append(try std.fmt.allocPrint(arena, "--sysroot={s}", .{sysroot})); + } + + if (target_util.llvmMachineAbi(target)) |mabi| { + try argv.appendSlice(&.{ + "-mllvm", + try std.fmt.allocPrint(arena, "-target-abi={s}", .{mabi}), + }); + } + + try argv.appendSlice(&.{ + "-mllvm", + try std.fmt.allocPrint(arena, "-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}), + }); + + if (comp.config.lto != .none) { + switch (comp.root_mod.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("--lto-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"), + } + } + switch (comp.root_mod.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + + if (elf.entry_name) |name| { + try argv.appendSlice(&.{ "--entry", name }); + } + + for (comp.force_undefined_symbols.keys()) |sym| { + try argv.append("-u"); + try argv.append(sym); + } + + switch (elf.hash_style) { + .gnu => try argv.append("--hash-style=gnu"), + .sysv => try argv.append("--hash-style=sysv"), + .both => {}, // this is the default + } + + if (output_mode == .Exe) { + try argv.appendSlice(&.{ + "-z", + try std.fmt.allocPrint(arena, "stack-size={d}", .{base.stack_size}), + }); + } + + switch (base.build_id) { + .none => try argv.append("--build-id=none"), + .fast, .uuid, .sha1, .md5 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ + @tagName(base.build_id), + })), + .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + })), + } + + try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{elf.image_base})); + + if (elf.linker_script) |linker_script| { + try argv.append("-T"); + try argv.append(linker_script); + } + + if (elf.sort_section) |how| { + const arg = try std.fmt.allocPrint(arena, "--sort-section={s}", .{@tagName(how)}); + try argv.append(arg); + } + + if (base.gc_sections) { + try argv.append("--gc-sections"); + } + + if (base.print_gc_sections) { + try argv.append("--print-gc-sections"); + } + + if (elf.print_icf_sections) { + try argv.append("--print-icf-sections"); + } + + if (elf.print_map) { + try argv.append("--print-map"); + } + + if (comp.link_eh_frame_hdr) { + try argv.append("--eh-frame-hdr"); + } + + if (elf.emit_relocs) { + try argv.append("--emit-relocs"); + } + + if (comp.config.rdynamic) { + try argv.append("--export-dynamic"); + } + + if (comp.config.debug_format == .strip) { + try argv.append("-s"); + } + + if (elf.z_nodelete) { + try argv.append("-z"); + try argv.append("nodelete"); + } + if (elf.z_notext) { + try argv.append("-z"); + try argv.append("notext"); + } + if (elf.z_defs) { + try argv.append("-z"); + try argv.append("defs"); + } + if (elf.z_origin) { + try argv.append("-z"); + try argv.append("origin"); + } + if (elf.z_nocopyreloc) { + try argv.append("-z"); + try argv.append("nocopyreloc"); + } + if (elf.z_now) { + // LLD defaults to -zlazy + try argv.append("-znow"); + } + if (!elf.z_relro) { + // LLD defaults to -zrelro + try argv.append("-znorelro"); + } + if (elf.z_common_page_size) |size| { + try argv.append("-z"); + try argv.append(try std.fmt.allocPrint(arena, "common-page-size={d}", .{size})); + } + if (elf.z_max_page_size) |size| { + try argv.append("-z"); + try argv.append(try std.fmt.allocPrint(arena, "max-page-size={d}", .{size})); + } + + if (getLDMOption(target)) |ldm| { + try argv.append("-m"); + try argv.append(ldm); + } + + if (link_mode == .static) { + if (target.cpu.arch.isArm()) { + try argv.append("-Bstatic"); + } else { + try argv.append("-static"); + } + } else if (switch (target.os.tag) { + else => is_dyn_lib, + .haiku => is_exe_or_dyn_lib, + }) { + try argv.append("-shared"); + } + + if (comp.config.pie and output_mode == .Exe) { + try argv.append("-pie"); + } + + if (is_exe_or_dyn_lib and target.os.tag == .netbsd) { + // Add options to produce shared objects with only 2 PT_LOAD segments. + // NetBSD expects 2 PT_LOAD segments in a shared object, otherwise + // ld.elf_so fails loading dynamic libraries with "not found" error. + // See https://github.com/ziglang/zig/issues/9109 . + try argv.append("--no-rosegment"); + try argv.append("-znorelro"); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + // csu prelude + const csu = try comp.getCrtPaths(arena); + if (csu.crt0) |p| try argv.append(try p.toString(arena)); + if (csu.crti) |p| try argv.append(try p.toString(arena)); + if (csu.crtbegin) |p| try argv.append(try p.toString(arena)); + + for (elf.rpath_list) |rpath| { + try argv.appendSlice(&.{ "-rpath", rpath }); + } + + for (elf.symbol_wrap_set) |symbol_name| { + try argv.appendSlice(&.{ "-wrap", symbol_name }); + } + + if (comp.config.link_libc) { + if (comp.libc_installation) |libc_installation| { + try argv.append("-L"); + try argv.append(libc_installation.crt_dir.?); + } + } + + if (have_dynamic_linker and + (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker)) + { + if (target.dynamic_linker.get()) |dynamic_linker| { + try argv.append("-dynamic-linker"); + try argv.append(dynamic_linker); + } + } + + if (is_dyn_lib) { + if (elf.soname) |soname| { + try argv.append("-soname"); + try argv.append(soname); + } + if (elf.version_script) |version_script| { + try argv.append("-version-script"); + try argv.append(version_script); + } + if (elf.allow_undefined_version) { + try argv.append("--undefined-version"); + } else { + try argv.append("--no-undefined-version"); + } + if (elf.enable_new_dtags) |enable_new_dtags| { + if (enable_new_dtags) { + try argv.append("--enable-new-dtags"); + } else { + try argv.append("--disable-new-dtags"); + } + } + } + + // Positional arguments to the linker such as object files. + var whole_archive = false; + + for (base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, // Windows-only + .dso => continue, + .object, .archive => |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + try argv.append(try obj.path.toString(arena)); + }, + .dso_exact => |dso_exact| { + assert(dso_exact.name[0] == ':'); + try argv.appendSlice(&.{ "-l", dso_exact.name }); + }, + }; + + if (whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(try key.status.success.object_path.toString(arena)); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + if (comp.tsan_lib) |lib| { + assert(comp.config.any_sanitize_thread); + try argv.append(try lib.full_object_path.toString(arena)); + } + + if (comp.fuzzer_lib) |lib| { + assert(comp.config.any_fuzz); + try argv.append(try lib.full_object_path.toString(arena)); + } + + if (ubsan_rt_path) |p| { + try argv.append(try p.toString(arena)); + } + + // Shared libraries. + if (is_exe_or_dyn_lib) { + // Worst-case, we need an --as-needed argument for every lib, as well + // as one before and one after. + try argv.ensureUnusedCapacity(2 * base.comp.link_inputs.len + 2); + argv.appendAssumeCapacity("--as-needed"); + var as_needed = true; + + for (base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, // Windows-only + .object, .archive, .dso_exact => continue, + .dso => |dso| { + const lib_as_needed = !dso.needed; + switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { + 0b00, 0b11 => {}, + 0b01 => { + argv.appendAssumeCapacity("--no-as-needed"); + as_needed = false; + }, + 0b10 => { + argv.appendAssumeCapacity("--as-needed"); + as_needed = true; + }, + } + + // By this time, we depend on these libs being dynamically linked + // libraries and not static libraries (the check for that needs to be earlier), + // but they could be full paths to .so files, in which case we + // want to avoid prepending "-l". + argv.appendAssumeCapacity(try dso.path.toString(arena)); + }, + }; + + if (!as_needed) { + argv.appendAssumeCapacity("--as-needed"); + as_needed = true; + } + + // libc++ dep + if (comp.config.link_libcpp) { + try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); + try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); + } + + // libunwind dep + if (comp.config.link_libunwind) { + try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); + } + + // libc dep + diags.flags.missing_libc = false; + if (comp.config.link_libc) { + if (comp.libc_installation != null) { + const needs_grouping = link_mode == .static; + if (needs_grouping) try argv.append("--start-group"); + try argv.appendSlice(target_util.libcFullLinkFlags(target)); + if (needs_grouping) try argv.append("--end-group"); + } else if (target.isGnuLibC()) { + for (glibc.libs) |lib| { + if (lib.removed_in) |rem_in| { + if (target.os.versionRange().gnuLibCVersion().?.order(rem_in) != .lt) continue; + } + + const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ + comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a")); + } else if (target.isMuslLibC()) { + try argv.append(try comp.crtFileAsString(arena, switch (link_mode) { + .static => "libc.a", + .dynamic => "libc.so", + })); + } else if (target.isFreeBSDLibC()) { + for (freebsd.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ + comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + } else if (target.isNetBSDLibC()) { + for (netbsd.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ + comp.netbsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + } else { + diags.flags.missing_libc = true; + } + + if (comp.zigc_static_lib) |zigc| { + try argv.append(try zigc.full_object_path.toString(arena)); + } + } + } + + // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs + // to be after the shared libraries, so they are picked up from the shared + // libraries, not libcompiler_rt. + if (compiler_rt_path) |p| { + try argv.append(try p.toString(arena)); + } + + // crt postlude + if (csu.crtend) |p| try argv.append(try p.toString(arena)); + if (csu.crtn) |p| try argv.append(try p.toString(arena)); + + if (base.allow_shlib_undefined) { + try argv.append("--allow-shlib-undefined"); + } + + switch (elf.compress_debug_sections) { + .none => {}, + .zlib => try argv.append("--compress-debug-sections=zlib"), + .zstd => try argv.append("--compress-debug-sections=zstd"), + } + + if (elf.bind_global_refs_locally) { + try argv.append("-Bsymbolic"); + } + + try spawnLld(comp, arena, argv.items); + } + + if (!lld.disable_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + std.Build.Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + base.lock = man.toOwnedLock(); + } +} +fn getLDMOption(target: std.Target) ?[]const u8 { + // This should only return emulations understood by LLD's parseEmulation(). + return switch (target.cpu.arch) { + .aarch64 => switch (target.os.tag) { + .linux => "aarch64linux", + else => "aarch64elf", + }, + .aarch64_be => switch (target.os.tag) { + .linux => "aarch64linuxb", + else => "aarch64elfb", + }, + .amdgcn => "elf64_amdgpu", + .arm, .thumb => switch (target.os.tag) { + .linux => "armelf_linux_eabi", + else => "armelf", + }, + .armeb, .thumbeb => switch (target.os.tag) { + .linux => "armelfb_linux_eabi", + else => "armelfb", + }, + .hexagon => "hexagonelf", + .loongarch32 => "elf32loongarch", + .loongarch64 => "elf64loongarch", + .mips => switch (target.os.tag) { + .freebsd => "elf32btsmip_fbsd", + else => "elf32btsmip", + }, + .mipsel => switch (target.os.tag) { + .freebsd => "elf32ltsmip_fbsd", + else => "elf32ltsmip", + }, + .mips64 => switch (target.os.tag) { + .freebsd => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32btsmipn32_fbsd", + else => "elf64btsmip_fbsd", + }, + else => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32btsmipn32", + else => "elf64btsmip", + }, + }, + .mips64el => switch (target.os.tag) { + .freebsd => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32ltsmipn32_fbsd", + else => "elf64ltsmip_fbsd", + }, + else => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32ltsmipn32", + else => "elf64ltsmip", + }, + }, + .msp430 => "msp430elf", + .powerpc => switch (target.os.tag) { + .freebsd => "elf32ppc_fbsd", + .linux => "elf32ppclinux", + else => "elf32ppc", + }, + .powerpcle => switch (target.os.tag) { + .linux => "elf32lppclinux", + else => "elf32lppc", + }, + .powerpc64 => "elf64ppc", + .powerpc64le => "elf64lppc", + .riscv32 => "elf32lriscv", + .riscv64 => "elf64lriscv", + .s390x => "elf64_s390", + .sparc64 => "elf64_sparc", + .x86 => switch (target.os.tag) { + .freebsd => "elf_i386_fbsd", + else => "elf_i386", + }, + .x86_64 => switch (target.abi) { + .gnux32, .muslx32 => "elf32_x86_64", + else => "elf_x86_64", + }, + else => null, + }; +} +fn wasmLink(lld: *Lld, arena: Allocator) !void { + const comp = lld.base.comp; + const shared_memory = comp.config.shared_memory; + const export_memory = comp.config.export_memory; + const import_memory = comp.config.import_memory; + const target = comp.root_mod.resolved_target.result; + const base = &lld.base; + const wasm = &lld.ofmt.wasm; + + const gpa = comp.gpa; + + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else { + break :p base.zcu_object_sub_path.?; + } + } else null; + + const is_obj = comp.config.output_mode == .Obj; + const compiler_rt_path: ?Cache.Path = blk: { + if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path; + if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; + break :blk null; + }; + const ubsan_rt_path: ?Cache.Path = blk: { + if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path; + if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path; + break :blk null; + }; + + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 14); + + try link.hashInputs(&man, comp.link_inputs); + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); + man.hash.addOptionalBytes(wasm.entry_name); + man.hash.add(base.stack_size); + man.hash.add(base.build_id); + man.hash.add(import_memory); + man.hash.add(export_memory); + man.hash.add(wasm.import_table); + man.hash.add(wasm.export_table); + man.hash.addOptional(wasm.initial_memory); + man.hash.addOptional(wasm.max_memory); + man.hash.add(shared_memory); + man.hash.addOptional(wasm.global_base); + man.hash.addListOfBytes(wasm.export_symbol_names); + // strip does not need to go into the linker hash because it is part of the hash namespace + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + base.lock = man.toOwnedLock(); + return; + } + log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (is_obj) { + // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk Cache.Path.initCwd(p); + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + try fs.Dir.copyFile( + the_object_path.root_dir.handle, + the_object_path.sub_path, + directory.handle, + base.emit.sub_path, + .{}, + ); + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(gpa); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + const linker_command = "wasm-ld"; + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + try argv.append("--error-limit=0"); + + if (comp.config.lto != .none) { + switch (comp.root_mod.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } + + if (import_memory) { + try argv.append("--import-memory"); + } + + if (export_memory) { + try argv.append("--export-memory"); + } + + if (wasm.import_table) { + assert(!wasm.export_table); + try argv.append("--import-table"); + } + + if (wasm.export_table) { + assert(!wasm.import_table); + try argv.append("--export-table"); + } + + // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly + // specified it as garbage collection is enabled by default. + if (!base.gc_sections) { + try argv.append("--no-gc-sections"); + } + + if (comp.config.debug_format == .strip) { + try argv.append("-s"); + } + + if (wasm.initial_memory) |initial_memory| { + const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory}); + try argv.append(arg); + } + + if (wasm.max_memory) |max_memory| { + const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory}); + try argv.append(arg); + } + + if (shared_memory) { + try argv.append("--shared-memory"); + } + + if (wasm.global_base) |global_base| { + const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base}); + try argv.append(arg); + } else { + // We prepend it by default, so when a stack overflow happens the runtime will trap correctly, + // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496 + // + // The user can overwrite this behavior by setting the global-base + try argv.append("--stack-first"); + } + + // Users are allowed to specify which symbols they want to export to the wasm host. + for (wasm.export_symbol_names) |symbol_name| { + const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name}); + try argv.append(arg); + } + + if (comp.config.rdynamic) { + try argv.append("--export-dynamic"); + } + + if (wasm.entry_name) |entry_name| { + try argv.appendSlice(&.{ "--entry", entry_name }); + } else { + try argv.append("--no-entry"); + } + + try argv.appendSlice(&.{ + "-z", + try std.fmt.allocPrint(arena, "stack-size={d}", .{base.stack_size}), + }); + + switch (base.build_id) { + .none => try argv.append("--build-id=none"), + .fast, .uuid, .sha1 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ + @tagName(base.build_id), + })), + .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + })), + .md5 => {}, + } + + if (wasm.import_symbols) { + try argv.append("--allow-undefined"); + } + + if (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic) { + try argv.append("--shared"); + } + if (comp.config.pie) { + try argv.append("--pie"); + } + + try argv.appendSlice(&.{ "-o", full_out_path }); + + if (target.cpu.arch == .wasm64) { + try argv.append("-mwasm64"); + } + + const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or + (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); + + if (comp.config.link_libc and is_exe_or_dyn_lib) { + if (target.os.tag == .wasi) { + for (comp.wasi_emulated_libs) |crt_file| { + try argv.append(try comp.crtFileAsString( + arena, + wasi_libc.emulatedLibCRFileLibName(crt_file), + )); + } + + try argv.append(try comp.crtFileAsString( + arena, + wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model), + )); + try argv.append(try comp.crtFileAsString(arena, "libc.a")); + } + + if (comp.zigc_static_lib) |zigc| { + try argv.append(try zigc.full_object_path.toString(arena)); + } + + if (comp.config.link_libcpp) { + try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); + try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); + } + } + + // Positional arguments to the linker such as object files. + var whole_archive = false; + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive => |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + try argv.append(try obj.path.toString(arena)); + }, + .dso => |dso| { + try argv.append(try dso.path.toString(arena)); + }, + .dso_exact => unreachable, + .res => unreachable, + }; + if (whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(try key.status.success.object_path.toString(arena)); + } + if (module_obj_path) |p| { + try argv.append(p); + } + + if (compiler_rt_path) |p| { + try argv.append(try p.toString(arena)); + } + + if (ubsan_rt_path) |p| { + try argv.append(try p.toStringZ(arena)); + } + + try spawnLld(comp, arena, argv.items); + + // Give +x to the .wasm file if it is an executable and the OS is WASI. + // Some systems may be configured to execute such binaries directly. Even if that + // is not the case, it means we will get "exec format error" when trying to run + // it, and then can react to that in the same way as trying to run an ELF file + // from a foreign CPU architecture. + if (fs.has_executable_bit and target.os.tag == .wasi and + comp.config.output_mode == .Exe) + { + // TODO: what's our strategy for reporting linker errors from this function? + // report a nice error here with the file path if it fails instead of + // just returning the error code. + // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. + std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { + error.OperationNotSupported => unreachable, // Not a symlink. + else => |e| return e, + }; + } + } + + if (!lld.disable_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + base.lock = man.toOwnedLock(); + } +} + +fn spawnLld( + comp: *Compilation, + arena: Allocator, + argv: []const []const u8, +) !void { + if (comp.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv[1..]); + } + + // If possible, we run LLD as a child process because it does not always + // behave properly as a library, unfortunately. + // https://github.com/ziglang/zig/issues/3825 + if (!std.process.can_spawn) { + const exit_code = try lldMain(arena, argv, false); + if (exit_code == 0) return; + if (comp.clang_passthrough_mode) std.process.exit(exit_code); + return error.LinkFailure; + } + + var stderr: []u8 = &.{}; + defer comp.gpa.free(stderr); + + var child = std.process.Child.init(argv, arena); + const term = (if (comp.clang_passthrough_mode) term: { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + break :term child.spawnAndWait(); + } else term: { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + child.spawn() catch |err| break :term err; + stderr = try child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize)); + break :term child.wait(); + }) catch |first_err| term: { + const err = switch (first_err) { + error.NameTooLong => err: { + const s = fs.path.sep_str; + const rand_int = std.crypto.random.int(u64); + const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; + + const rsp_file = try comp.dirs.local_cache.handle.createFileZ(rsp_path, .{}); + defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err| + log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) }); + { + defer rsp_file.close(); + var rsp_buf = std.io.bufferedWriter(rsp_file.writer()); + const rsp_writer = rsp_buf.writer(); + for (argv[2..]) |arg| { + try rsp_writer.writeByte('"'); + for (arg) |c| { + switch (c) { + '\"', '\\' => try rsp_writer.writeByte('\\'), + else => {}, + } + try rsp_writer.writeByte(c); + } + try rsp_writer.writeByte('"'); + try rsp_writer.writeByte('\n'); + } + try rsp_buf.flush(); + } + + var rsp_child = std.process.Child.init(&.{ argv[0], argv[1], try std.fmt.allocPrint( + arena, + "@{s}", + .{try comp.dirs.local_cache.join(arena, &.{rsp_path})}, + ) }, arena); + if (comp.clang_passthrough_mode) { + rsp_child.stdin_behavior = .Inherit; + rsp_child.stdout_behavior = .Inherit; + rsp_child.stderr_behavior = .Inherit; + + break :term rsp_child.spawnAndWait() catch |err| break :err err; + } else { + rsp_child.stdin_behavior = .Ignore; + rsp_child.stdout_behavior = .Ignore; + rsp_child.stderr_behavior = .Pipe; + + rsp_child.spawn() catch |err| break :err err; + stderr = try rsp_child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize)); + break :term rsp_child.wait() catch |err| break :err err; + } + }, + else => first_err, + }; + log.err("unable to spawn LLD {s}: {s}", .{ argv[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + const diags = &comp.link_diags; + switch (term) { + .Exited => |code| if (code != 0) { + if (comp.clang_passthrough_mode) std.process.exit(code); + diags.lockAndParseLldStderr(argv[1], stderr); + return error.LinkFailure; + }, + else => { + if (comp.clang_passthrough_mode) std.process.abort(); + return diags.fail("{s} terminated with stderr:\n{s}", .{ argv[0], stderr }); + }, + } + + if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr}); +} + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Cache = std.Build.Cache; +const allocPrint = std.fmt.allocPrint; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; + +const Compilation = @import("../Compilation.zig"); +const Zcu = @import("../Zcu.zig"); +const dev = @import("../dev.zig"); +const freebsd = @import("../libs/freebsd.zig"); +const glibc = @import("../libs/glibc.zig"); +const netbsd = @import("../libs/netbsd.zig"); +const wasi_libc = @import("../libs/wasi_libc.zig"); +const link = @import("../link.zig"); +const lldMain = @import("../main.zig").lldMain; +const target_util = @import("../target.zig"); +const trace = @import("../tracy.zig").trace; +const Lld = @This(); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6667ed6a63..2c30b34215 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -194,7 +194,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = allow_shlib_undefined, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .rpath_list = options.rpath_list, @@ -227,7 +226,7 @@ pub fn createEmpty( self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(false, output_mode, link_mode), + .mode = link.File.determineMode(output_mode, link_mode), }); // Append null file @@ -342,15 +341,6 @@ pub fn flush( tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { - try self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu( - self: *MachO, - arena: Allocator, - tid: Zcu.PerThread.Id, - prog_node: std.Progress.Node, -) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -373,7 +363,7 @@ pub fn flushZcu( // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); - if (self.getZigObject()) |zo| try zo.flushZcu(self, tid); + if (self.getZigObject()) |zo| try zo.flush(self, tid); if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path); if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path); @@ -617,7 +607,7 @@ pub fn flushZcu( error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to calculate and write uuid: {s}", .{@errorName(e)}), }; - if (self.getDebugSymbols()) |dsym| dsym.flushZcu(self) catch |err| switch (err) { + if (self.getDebugSymbols()) |dsym| dsym.flush(self) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to get debug symbols: {s}", .{@errorName(e)}), }; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 8579863d03..eef3492b48 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -178,7 +178,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 return offset; } -pub fn flushZcu(self: *DebugSymbols, macho_file: *MachO) !void { +pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { const zo = macho_file.getZigObject().?; for (self.relocs.items) |*reloc| { const sym = zo.symbols.items[reloc.target]; diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 4d99afc61a..13ebb40cf9 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -550,7 +550,7 @@ pub fn getInputSection(self: ZigObject, atom: Atom, macho_file: *MachO) macho.se return sect; } -pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void { +pub fn flush(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void { const diags = &macho_file.base.comp.link_diags; // Handle any lazy symbols that were emitted by incremental compilation. @@ -589,7 +589,7 @@ pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) lin if (self.dwarf) |*dwarf| { const pt: Zcu.PerThread = .activate(macho_file.base.comp.zcu.?, tid); defer pt.deactivate(); - dwarf.flushZcu(pt) catch |err| switch (err) { + dwarf.flush(pt) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to flush dwarf module: {s}", .{@errorName(e)}), }; @@ -599,7 +599,7 @@ pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) lin self.debug_strtab_dirty = false; } - // The point of flushZcu() is to commit changes, so in theory, nothing should + // The point of flush() is to commit changes, so in theory, nothing should // be dirty after this. However, it is possible for some things to remain // dirty because they fail to be written in the event of compile errors, // such as debug_line_header_dirty and debug_info_header_dirty. @@ -1537,7 +1537,7 @@ pub fn getOrCreateMetadataForLazySymbol( } state_ptr.* = .pending_flush; const symbol_index = symbol_index_ptr.*; - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(macho_file, pt, lazy_sym, symbol_index); return symbol_index; } diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 0a940cb0b3..c487169b3f 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -301,7 +301,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .sixtyfour_bit = sixtyfour_bit, @@ -494,7 +493,7 @@ fn updateFinish(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index // write the symbol // we already have the got index const sym: aout.Sym = .{ - .value = undefined, // the value of stuff gets filled in in flushZcu + .value = undefined, // the value of stuff gets filled in in flush .type = atom.type, .name = try gpa.dupe(u8, nav.name.toSlice(ip)), }; @@ -527,25 +526,6 @@ fn allocateGotIndex(self: *Plan9) usize { } } -pub fn flush( - self: *Plan9, - arena: Allocator, - tid: Zcu.PerThread.Id, - prog_node: std.Progress.Node, -) link.File.FlushError!void { - const comp = self.base.comp; - const diags = &comp.link_diags; - const use_lld = build_options.have_llvm and comp.config.use_lld; - assert(!use_lld); - - switch (link.File.effectiveOutputMode(use_lld, comp.config.output_mode)) { - .Exe => {}, - .Obj => return diags.fail("writing plan9 object files unimplemented", .{}), - .Lib => return diags.fail("writing plan9 lib files unimplemented", .{}), - } - return self.flushZcu(arena, tid, prog_node); -} - pub fn changeLine(l: *std.ArrayList(u8), delta_line: i32) !void { if (delta_line > 0 and delta_line < 65) { const toappend = @as(u8, @intCast(delta_line)); @@ -586,7 +566,7 @@ fn atomCount(self: *Plan9) usize { return data_nav_count + fn_nav_count + lazy_atom_count + extern_atom_count + uav_atom_count; } -pub fn flushZcu( +pub fn flush( self: *Plan9, arena: Allocator, /// TODO: stop using this @@ -607,10 +587,16 @@ pub fn flushZcu( const gpa = comp.gpa; const target = comp.root_mod.resolved_target.result; + switch (comp.config.output_mode) { + .Exe => {}, + .Obj => return diags.fail("writing plan9 object files unimplemented", .{}), + .Lib => return diags.fail("writing plan9 lib files unimplemented", .{}), + } + const sub_prog_node = prog_node.start("Flush Module", 0); defer sub_prog_node.end(); - log.debug("flushZcu", .{}); + log.debug("flush", .{}); defer assert(self.hdr.entry != 0x0); @@ -1039,7 +1025,7 @@ pub fn getOrCreateAtomForLazySymbol(self: *Plan9, pt: Zcu.PerThread, lazy_sym: F const atom = atom_ptr.*; _ = try self.getAtomPtr(atom).getOrCreateSymbolTableEntry(self); _ = self.getAtomPtr(atom).getOrCreateOffsetTableEntry(self); - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbolAtom(pt, lazy_sym, atom); return atom; } @@ -1182,11 +1168,7 @@ pub fn open( const file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .read = true, - .mode = link.File.determineMode( - use_lld, - comp.config.output_mode, - comp.config.link_mode, - ), + .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), }); errdefer file.close(); self.base.file = file; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index c6e86895f5..8b6b99525f 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -17,7 +17,7 @@ //! All regular functions. // Because SPIR-V requires re-compilation anyway, and so hot swapping will not work -// anyway, we simply generate all the code in flushZcu. This keeps +// anyway, we simply generate all the code in flush. This keeps // things considerably simpler. const SpirV = @This(); @@ -83,7 +83,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 0, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .object = codegen.Object.init(gpa, comp.getTarget()), @@ -193,18 +192,14 @@ pub fn updateExports( // TODO: Export regular functions, variables, etc using Linkage attributes. } -pub fn flush(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu( +pub fn flush( self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { // The goal is to never use this because it's only needed if we need to - // write to InternPool, but flushZcu is too late to be writing to the + // write to InternPool, but flush is too late to be writing to the // InternPool. _ = tid; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e92be32b7d..5c804ed21f 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -40,7 +40,6 @@ const Zcu = @import("../Zcu.zig"); const codegen = @import("../codegen.zig"); const dev = @import("../dev.zig"); const link = @import("../link.zig"); -const lldMain = @import("../main.zig").lldMain; const trace = @import("../tracy.zig").trace; const wasi_libc = @import("../libs/wasi_libc.zig"); const Value = @import("../Value.zig"); @@ -74,8 +73,6 @@ global_base: ?u64, initial_memory: ?u64, /// When defined, sets the maximum memory size of the memory. max_memory: ?u64, -/// When true, will import the function table from the host environment. -import_table: bool, /// When true, will export the function table to the host environment. export_table: bool, /// Output name of the file @@ -2935,17 +2932,14 @@ pub fn createEmpty( const target = comp.root_mod.resolved_target.result; assert(target.ofmt == .wasm); - const use_lld = build_options.have_llvm and comp.config.use_lld; const use_llvm = comp.config.use_llvm; const output_mode = comp.config.output_mode; const wasi_exec_model = comp.config.wasi_exec_model; - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. // If using LLVM to generate the object file for the zig compilation unit, // we need a place to put the object file so that it can be subsequently // handled. - const zcu_object_sub_path = if (!use_lld and !use_llvm) + const zcu_object_sub_path = if (!use_llvm) null else try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); @@ -2970,13 +2964,11 @@ pub fn createEmpty( }, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .name = undefined, .string_table = .empty, .string_bytes = .empty, - .import_table = options.import_table, .export_table = options.export_table, .import_symbols = options.import_symbols, .export_symbol_names = options.export_symbol_names, @@ -3004,17 +2996,7 @@ pub fn createEmpty( .named => |name| (try wasm.internString(name)).toOptional(), }; - if (use_lld and (use_llvm or !comp.config.have_zcu)) { - // LLVM emits the object file (if any); LLD links it into the final product. - return wasm; - } - - // What path should this Wasm linker code output to? - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. - const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; - - wasm.base.file = try emit.root_dir.handle.createFile(sub_path, .{ + wasm.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, .mode = if (fs.has_executable_bit) @@ -3025,7 +3007,7 @@ pub fn createEmpty( else 0, }); - wasm.name = sub_path; + wasm.name = emit.sub_path; return wasm; } @@ -3367,21 +3349,6 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { } } -pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - const comp = wasm.base.comp; - const use_lld = build_options.have_llvm and comp.config.use_lld; - const diags = &comp.link_diags; - - if (use_lld) { - return wasm.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), - }; - } - return wasm.flushZcu(arena, tid, prog_node); -} - pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -3773,14 +3740,14 @@ fn markTable(wasm: *Wasm, i: ObjectTableIndex) link.File.FlushError!void { try wasm.tables.put(wasm.base.comp.gpa, .fromObjectTable(i), {}); } -pub fn flushZcu( +pub fn flush( wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { // The goal is to never use this because it's only needed if we need to - // write to InternPool, but flushZcu is too late to be writing to the + // write to InternPool, but flush is too late to be writing to the // InternPool. _ = tid; const comp = wasm.base.comp; @@ -3832,436 +3799,6 @@ pub fn flushZcu( }; } -fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { - dev.check(.lld_linker); - - const tracy = trace(@src()); - defer tracy.end(); - - const comp = wasm.base.comp; - const diags = &comp.link_diags; - const shared_memory = comp.config.shared_memory; - const export_memory = comp.config.export_memory; - const import_memory = comp.config.import_memory; - const target = comp.root_mod.resolved_target.result; - - const gpa = comp.gpa; - - const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { - if (zcu.llvm_object == null) { - try wasm.flushZcu(arena, tid, prog_node); - } else { - // `Compilation.flush` has already made LLVM emit this object file for us. - } - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? }); - } else { - break :blk wasm.base.zcu_object_sub_path.?; - } - } else null; - - const sub_prog_node = prog_node.start("LLD Link", 0); - defer sub_prog_node.end(); - - const is_obj = comp.config.output_mode == .Obj; - const compiler_rt_path: ?Path = blk: { - if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path; - if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; - break :blk null; - }; - const ubsan_rt_path: ?Path = blk: { - if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path; - if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path; - break :blk null; - }; - - const id_symlink_basename = "lld.id"; - - var man: Cache.Manifest = undefined; - defer if (!wasm.base.disable_lld_caching) man.deinit(); - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!wasm.base.disable_lld_caching) { - man = comp.cache_parent.obtain(); - - // We are about to obtain this lock, so here we give other processes a chance first. - wasm.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 14); - - try link.hashInputs(&man, comp.link_inputs); - for (comp.c_object_table.keys()) |key| { - _ = try man.addFilePath(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - try man.addOptionalFilePath(compiler_rt_path); - try man.addOptionalFilePath(ubsan_rt_path); - man.hash.addOptionalBytes(wasm.entry_name.slice(wasm)); - man.hash.add(wasm.base.stack_size); - man.hash.add(wasm.base.build_id); - man.hash.add(import_memory); - man.hash.add(export_memory); - man.hash.add(wasm.import_table); - man.hash.add(wasm.export_table); - man.hash.addOptional(wasm.initial_memory); - man.hash.addOptional(wasm.max_memory); - man.hash.add(shared_memory); - man.hash.addOptional(wasm.global_base); - man.hash.addListOfBytes(wasm.export_symbol_names); - // strip does not need to go into the linker hash because it is part of the hash namespace - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - wasm.base.lock = man.toOwnedLock(); - return; - } - log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - if (is_obj) { - // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk Path.initCwd(p); - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - try fs.Dir.copyFile( - the_object_path.root_dir.handle, - the_object_path.sub_path, - directory.handle, - wasm.base.emit.sub_path, - .{}, - ); - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(gpa); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - const linker_command = "wasm-ld"; - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); - try argv.append("--error-limit=0"); - - if (comp.config.lto != .none) { - switch (comp.root_mod.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), - } - } - - if (import_memory) { - try argv.append("--import-memory"); - } - - if (export_memory) { - try argv.append("--export-memory"); - } - - if (wasm.import_table) { - assert(!wasm.export_table); - try argv.append("--import-table"); - } - - if (wasm.export_table) { - assert(!wasm.import_table); - try argv.append("--export-table"); - } - - // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly - // specified it as garbage collection is enabled by default. - if (!wasm.base.gc_sections) { - try argv.append("--no-gc-sections"); - } - - if (comp.config.debug_format == .strip) { - try argv.append("-s"); - } - - if (wasm.initial_memory) |initial_memory| { - const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory}); - try argv.append(arg); - } - - if (wasm.max_memory) |max_memory| { - const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory}); - try argv.append(arg); - } - - if (shared_memory) { - try argv.append("--shared-memory"); - } - - if (wasm.global_base) |global_base| { - const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base}); - try argv.append(arg); - } else { - // We prepend it by default, so when a stack overflow happens the runtime will trap correctly, - // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496 - // - // The user can overwrite this behavior by setting the global-base - try argv.append("--stack-first"); - } - - // Users are allowed to specify which symbols they want to export to the wasm host. - for (wasm.export_symbol_names) |symbol_name| { - const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name}); - try argv.append(arg); - } - - if (comp.config.rdynamic) { - try argv.append("--export-dynamic"); - } - - if (wasm.entry_name.slice(wasm)) |entry_name| { - try argv.appendSlice(&.{ "--entry", entry_name }); - } else { - try argv.append("--no-entry"); - } - - try argv.appendSlice(&.{ - "-z", - try std.fmt.allocPrint(arena, "stack-size={d}", .{wasm.base.stack_size}), - }); - - switch (wasm.base.build_id) { - .none => try argv.append("--build-id=none"), - .fast, .uuid, .sha1 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ - @tagName(wasm.base.build_id), - })), - .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ - std.fmt.fmtSliceHexLower(hs.toSlice()), - })), - .md5 => {}, - } - - if (wasm.import_symbols) { - try argv.append("--allow-undefined"); - } - - if (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic) { - try argv.append("--shared"); - } - if (comp.config.pie) { - try argv.append("--pie"); - } - - try argv.appendSlice(&.{ "-o", full_out_path }); - - if (target.cpu.arch == .wasm64) { - try argv.append("-mwasm64"); - } - - const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or - (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); - - if (comp.config.link_libc and is_exe_or_dyn_lib) { - if (target.os.tag == .wasi) { - for (comp.wasi_emulated_libs) |crt_file| { - try argv.append(try comp.crtFileAsString( - arena, - wasi_libc.emulatedLibCRFileLibName(crt_file), - )); - } - - try argv.append(try comp.crtFileAsString( - arena, - wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model), - )); - try argv.append(try comp.crtFileAsString(arena, "libc.a")); - } - - if (comp.zigc_static_lib) |zigc| { - try argv.append(try zigc.full_object_path.toString(arena)); - } - - if (comp.config.link_libcpp) { - try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); - try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); - } - } - - // Positional arguments to the linker such as object files. - var whole_archive = false; - for (comp.link_inputs) |link_input| switch (link_input) { - .object, .archive => |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - try argv.append(try obj.path.toString(arena)); - }, - .dso => |dso| { - try argv.append(try dso.path.toString(arena)); - }, - .dso_exact => unreachable, - .res => unreachable, - }; - if (whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(try key.status.success.object_path.toString(arena)); - } - if (module_obj_path) |p| { - try argv.append(p); - } - - if (compiler_rt_path) |p| { - try argv.append(try p.toString(arena)); - } - - if (ubsan_rt_path) |p| { - try argv.append(try p.toStringZ(arena)); - } - - if (comp.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } - - if (std.process.can_spawn) { - // If possible, we run LLD as a child process because it does not always - // behave properly as a library, unfortunately. - // https://github.com/ziglang/zig/issues/3825 - var child = std.process.Child.init(argv.items, arena); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - - const term = child.spawnAndWait() catch |err| { - log.err("failed to spawn (passthrough mode) LLD {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnWasm; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - std.process.exit(code); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const stderr = try child.stderr.?.reader().readAllAlloc(arena, std.math.maxInt(usize)); - - const term = child.wait() catch |err| { - log.err("failed to spawn LLD {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnWasm; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - diags.lockAndParseLldStderr(linker_command, stderr); - return error.LinkFailure; - } - }, - else => { - return diags.fail("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - }, - } - - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); - } - } - } else { - const exit_code = try lldMain(arena, argv.items, false); - if (exit_code != 0) { - if (comp.clang_passthrough_mode) { - std.process.exit(exit_code); - } else { - return diags.fail("{s} returned exit code {d}:\n{s}", .{ argv.items[0], exit_code }); - } - } - } - - // Give +x to the .wasm file if it is an executable and the OS is WASI. - // Some systems may be configured to execute such binaries directly. Even if that - // is not the case, it means we will get "exec format error" when trying to run - // it, and then can react to that in the same way as trying to run an ELF file - // from a foreign CPU architecture. - if (fs.has_executable_bit and target.os.tag == .wasi and - comp.config.output_mode == .Exe) - { - // TODO: what's our strategy for reporting linker errors from this function? - // report a nice error here with the file path if it fails instead of - // just returning the error code. - // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. - std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { - error.OperationNotSupported => unreachable, // Not a symlink. - else => |e| return e, - }; - } - } - - if (!wasm.base.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - wasm.base.lock = man.toOwnedLock(); - } -} - fn defaultEntrySymbolName( preloaded_strings: *const PreloadedStrings, wasi_exec_model: std.builtin.WasiExecModel, diff --git a/src/link/Xcoff.zig b/src/link/Xcoff.zig index e2f81e015e..7fe714ce6e 100644 --- a/src/link/Xcoff.zig +++ b/src/link/Xcoff.zig @@ -46,7 +46,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 0, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, }; @@ -105,10 +104,6 @@ pub fn updateExports( } pub fn flush(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = self; _ = arena; _ = tid; |
