link: divorce LLD from the self-hosted linkers

Similar to the previous commit, this commit untangles LLD integration
from the self-hosted linkers. Despite the big network of functions which
were involved, it turns out what was going on here is quite simple. The
LLD linking logic is actually very self-contained; it requires a few
flags from the `link.File.OpenOptions`, but that's really about it. We
don't need any of the mutable state on `Elf`/`Coff`/`Wasm`, for
instance. There was some legacy code trying to handle support for using
self-hosted codegen with LLD, but that's not a supported use case, so
I've just stripped it out.

For now, I've just pasted the logic for linking the 3 targets we
currently support using LLD for into this new linker implementation,
`link.Lld`; however, it's almost certainly possible to combine some of
the logic and simplify this file a bit. But to be honest, it's not
actually that bad right now.

This commit ends up eliminating the distinction between `flush` and
`flushZcu` (formerly `flushModule`) in linkers, where the latter
previously meant something along the lines of "flush, but if you're
going to be linking with LLD, just flush the ZCU object file, don't
actually link"?. The distinction here doesn't seem like it was properly
defined, and most linkers seem to treat them as essentially identical
anyway. Regardless, all calls to `flushZcu` are gone now, so it's
deleted -- one `flush` to rule them all!

The end result of this commit and the preceding one is that LLVM and LLD
fit into the pipeline much more sanely:

* If we're using LLVM for the ZCU, that state is on `zcu.llvm_object`
* If we're using LLD to link, then the `link.File` is a `link.Lld`
* Calls to "ZCU link functions" (e.g. `updateNav`) lower to calls to the
  LLVM object if it's available, or otherwise to the `link.File` if it's
  available (neither is available under `-fno-emit-bin`)
* After everything is done, linking is finalized by calling `flush` on
  the `link.File`; for `link.Lld` this invokes LLD, for other linkers it
  flushes self-hosted linker state

There's one messy thing remaining, and that's how self-hosted function
codegen in a ZCU works; right now, we process AIR with a call sequence
something like this:

* `link.doTask`
* `Zcu.PerThread.linkerUpdateFunc`
* `link.File.updateFunc`
* `link.Elf.updateFunc`
* `link.Elf.ZigObject.updateFunc`
* `codegen.generateFunction`
* `arch.x86_64.CodeGen.generate`

So, we start in the linker, take a scenic detour through `Zcu`, go back
to the linker, into its implementation, and then... right back out, into
code which is generic over the linker implementation, and then dispatch
on the *backend* instead! Of course, within `arch.x86_64.CodeGen`, there
are some more places which switch on the `link` implementation being
used. This is all pretty silly... so it shall be my next target.
This commit is contained in:
mlugg
2025-05-28 09:30:31 +01:00
parent 3743c3e39c
commit 2fb6f5c1ad
18 changed files with 2262 additions and 2284 deletions

View File

@@ -1592,9 +1592,9 @@ pub const CreateOptions = struct {
linker_tsaware: bool = false,
linker_nxcompat: bool = false,
linker_dynamicbase: bool = true,
linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null,
linker_compress_debug_sections: ?link.File.Lld.Elf.CompressDebugSections = null,
linker_module_definition_file: ?[]const u8 = null,
linker_sort_section: ?link.File.Elf.SortSection = null,
linker_sort_section: ?link.File.Lld.Elf.SortSection = null,
major_subsystem_version: ?u16 = null,
minor_subsystem_version: ?u16 = null,
clang_passthrough_mode: bool = false,
@@ -1616,7 +1616,7 @@ pub const CreateOptions = struct {
/// building such dependencies themselves, this flag must be set to avoid
/// infinite recursion.
skip_linker_dependencies: bool = false,
hash_style: link.File.Elf.HashStyle = .both,
hash_style: link.File.Lld.Elf.HashStyle = .both,
entry: Entry = .default,
force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .empty,
stack_size: ?u64 = null,

View File

@@ -1587,12 +1587,15 @@ pub const Object = struct {
const comp = zcu.comp;
// If we're on COFF and linking with LLD, the linker cares about our exports to determine the subsystem in use.
if (comp.bin_file != null and
comp.bin_file.?.tag == .coff and
zcu.comp.config.use_lld and
ip.isFunctionType(ip.getNav(nav_index).typeOf(ip)))
{
const flags = &comp.bin_file.?.cast(.coff).?.lld_export_flags;
coff_export_flags: {
const lf = comp.bin_file orelse break :coff_export_flags;
const lld = lf.cast(.lld) orelse break :coff_export_flags;
const coff = switch (lld.ofmt) {
.elf, .wasm => break :coff_export_flags,
.coff => |*coff| coff,
};
if (!ip.isFunctionType(ip.getNav(nav_index).typeOf(ip))) break :coff_export_flags;
const flags = &coff.lld_export_flags;
for (export_indices) |export_index| {
const name = export_index.ptr(zcu).opts.name;
if (name.eqlSlice("main", ip)) flags.c_main = true;

View File

@@ -19,7 +19,6 @@ const Zcu = @import("Zcu.zig");
const InternPool = @import("InternPool.zig");
const Type = @import("Type.zig");
const Value = @import("Value.zig");
const lldMain = @import("main.zig").lldMain;
const Package = @import("Package.zig");
const dev = @import("dev.zig");
const ThreadSafeQueue = @import("ThreadSafeQueue.zig").ThreadSafeQueue;
@@ -388,7 +387,6 @@ pub const File = struct {
/// When linking with LLD, this linker code will output an object file only at
/// this location, and then this path can be placed on the LLD linker line.
zcu_object_sub_path: ?[]const u8 = null,
disable_lld_caching: bool,
gc_sections: bool,
print_gc_sections: bool,
build_id: std.zig.BuildId,
@@ -424,7 +422,7 @@ pub const File = struct {
tsaware: bool,
nxcompat: bool,
dynamicbase: bool,
compress_debug_sections: Elf.CompressDebugSections,
compress_debug_sections: Lld.Elf.CompressDebugSections,
bind_global_refs_locally: bool,
import_symbols: bool,
import_table: bool,
@@ -436,8 +434,8 @@ pub const File = struct {
global_base: ?u64,
build_id: std.zig.BuildId,
disable_lld_caching: bool,
hash_style: Elf.HashStyle,
sort_section: ?Elf.SortSection,
hash_style: Lld.Elf.HashStyle,
sort_section: ?Lld.Elf.SortSection,
major_subsystem_version: ?u16,
minor_subsystem_version: ?u16,
gc_sections: ?bool,
@@ -521,12 +519,20 @@ pub const File = struct {
emit: Path,
options: OpenOptions,
) !*File {
if (comp.config.use_lld) {
dev.check(.lld_linker);
assert(comp.zcu == null or comp.config.use_llvm);
// LLD does not support incremental linking.
const lld: *Lld = try .createEmpty(arena, comp, emit, options);
return &lld.base;
}
switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) {
inline else => |tag| {
dev.check(tag.devFeature());
const ptr = try tag.Type().open(arena, comp, emit, options);
return &ptr.base;
},
.lld => unreachable, // not known from ofmt
}
}
@@ -536,12 +542,19 @@ pub const File = struct {
emit: Path,
options: OpenOptions,
) !*File {
if (comp.config.use_lld) {
dev.check(.lld_linker);
assert(comp.zcu == null or comp.config.use_llvm);
const lld: *Lld = try .createEmpty(arena, comp, emit, options);
return &lld.base;
}
switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) {
inline else => |tag| {
dev.check(tag.devFeature());
const ptr = try tag.Type().createEmpty(arena, comp, emit, options);
return &ptr.base;
},
.lld => unreachable, // not known from ofmt
}
}
@@ -554,6 +567,7 @@ pub const File = struct {
const comp = base.comp;
const gpa = comp.gpa;
switch (base.tag) {
.lld => assert(base.file == null),
.coff, .elf, .macho, .plan9, .wasm, .goff, .xcoff => {
if (base.file != null) return;
dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
@@ -586,13 +600,12 @@ pub const File = struct {
}
}
}
const use_lld = build_options.have_llvm and comp.config.use_lld;
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
.truncate = false,
.read = true,
.mode = determineMode(use_lld, output_mode, link_mode),
.mode = determineMode(output_mode, link_mode),
});
},
.c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),
@@ -618,7 +631,6 @@ pub const File = struct {
const comp = base.comp;
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
const use_lld = build_options.have_llvm and comp.config.use_lld;
switch (output_mode) {
.Obj => return,
@@ -629,13 +641,9 @@ pub const File = struct {
.Exe => {},
}
switch (base.tag) {
.lld => assert(base.file == null),
.elf => if (base.file) |f| {
dev.check(.elf_linker);
if (base.zcu_object_sub_path != null and use_lld) {
// The file we have open is not the final file that we want to
// make executable, so we don't have to close it.
return;
}
f.close();
base.file = null;
@@ -650,11 +658,6 @@ pub const File = struct {
},
.coff, .macho, .plan9, .wasm, .goff, .xcoff => if (base.file) |f| {
dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
if (base.zcu_object_sub_path != null) {
// The file we have open is not the final file that we want to
// make executable, so we don't have to close it.
return;
}
f.close();
base.file = null;
@@ -692,6 +695,7 @@ pub const File = struct {
pub fn getGlobalSymbol(base: *File, name: []const u8, lib_name: ?[]const u8) UpdateNavError!u32 {
log.debug("getGlobalSymbol '{s}' (expected in '{?s}')", .{ name, lib_name });
switch (base.tag) {
.lld => unreachable,
.plan9 => unreachable,
.spirv => unreachable,
.c => unreachable,
@@ -709,6 +713,7 @@ pub const File = struct {
const nav = pt.zcu.intern_pool.getNav(nav_index);
assert(nav.status == .fully_resolved);
switch (base.tag) {
.lld => unreachable,
inline else => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).updateNav(pt, nav_index);
@@ -726,6 +731,7 @@ pub const File = struct {
fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) UpdateContainerTypeError!void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
.lld => unreachable,
else => {},
inline .elf => |tag| {
dev.check(tag.devFeature());
@@ -746,6 +752,7 @@ pub const File = struct {
) UpdateNavError!void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
.lld => unreachable,
inline else => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, air, liveness);
@@ -772,6 +779,7 @@ pub const File = struct {
}
switch (base.tag) {
.lld => unreachable,
.spirv => {},
.goff, .xcoff => {},
inline else => |tag| {
@@ -811,8 +819,7 @@ pub const File = struct {
OutOfMemory,
};
/// Commit pending changes and write headers. Takes into account final output mode
/// and `use_lld`, not only `effectiveOutputMode`.
/// Commit pending changes and write headers. Takes into account final output mode.
/// `arena` has the lifetime of the call to `Compilation.update`.
pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
const comp = base.comp;
@@ -834,15 +841,7 @@ pub const File = struct {
};
return;
}
assert(base.post_prelink);
const use_lld = build_options.have_llvm and comp.config.use_lld;
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
if (use_lld and output_mode == .Lib and link_mode == .static) {
return base.linkAsArchive(arena, tid, prog_node);
}
switch (base.tag) {
inline else => |tag| {
dev.check(tag.devFeature());
@@ -851,19 +850,6 @@ pub const File = struct {
}
}
/// Commit pending changes and write headers. Works based on `effectiveOutputMode`
/// rather than final output mode.
/// Never called when LLVM is codegenning the ZCU.
fn flushZcu(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
inline else => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).flushZcu(arena, tid, prog_node);
},
}
}
pub const UpdateExportsError = error{
OutOfMemory,
AnalysisFail,
@@ -882,6 +868,7 @@ pub const File = struct {
) UpdateExportsError!void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
.lld => unreachable,
inline else => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).updateExports(pt, exported, export_indices);
@@ -911,6 +898,7 @@ pub const File = struct {
pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) !u64 {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
.lld => unreachable,
.c => unreachable,
.spirv => unreachable,
.wasm => unreachable,
@@ -932,6 +920,7 @@ pub const File = struct {
) !codegen.GenResult {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
.lld => unreachable,
.c => unreachable,
.spirv => unreachable,
.wasm => unreachable,
@@ -947,6 +936,7 @@ pub const File = struct {
pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) !u64 {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
.lld => unreachable,
.c => unreachable,
.spirv => unreachable,
.wasm => unreachable,
@@ -966,6 +956,8 @@ pub const File = struct {
) void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
.lld => unreachable,
.plan9,
.spirv,
.goff,
@@ -981,6 +973,7 @@ pub const File = struct {
/// Opens a path as an object file and parses it into the linker.
fn openLoadObject(base: *File, path: Path) anyerror!void {
if (base.tag == .lld) return;
const diags = &base.comp.link_diags;
const input = try openObjectInput(diags, path);
errdefer input.object.file.close();
@@ -990,6 +983,7 @@ pub const File = struct {
/// Opens a path as a static library and parses it into the linker.
/// If `query` is non-null, allows GNU ld scripts.
fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void {
if (base.tag == .lld) return;
if (opt_query) |query| {
const archive = try openObject(path, query.must_link, query.hidden);
errdefer archive.file.close();
@@ -1012,6 +1006,7 @@ pub const File = struct {
/// Opens a path as a shared library and parses it into the linker.
/// Handles GNU ld scripts.
fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {
if (base.tag == .lld) return;
const dso = try openDso(path, query.needed, query.weak, query.reexport);
errdefer dso.file.close();
loadInput(base, .{ .dso = dso }) catch |err| switch (err) {
@@ -1064,8 +1059,7 @@ pub const File = struct {
}
pub fn loadInput(base: *File, input: Input) anyerror!void {
const use_lld = build_options.have_llvm and base.comp.config.use_lld;
if (use_lld) return;
if (base.tag == .lld) return;
switch (base.tag) {
inline .elf, .wasm => |tag| {
dev.check(tag.devFeature());
@@ -1079,8 +1073,6 @@ pub const File = struct {
/// this, `loadInput` will not be called anymore.
pub fn prelink(base: *File, prog_node: std.Progress.Node) FlushError!void {
assert(!base.post_prelink);
const use_lld = build_options.have_llvm and base.comp.config.use_lld;
if (use_lld) return;
// In this case, an object file is created by the LLVM backend, so
// there is no prelink phase. The Zig code is linked as a standard
@@ -1096,170 +1088,6 @@ pub const File = struct {
}
}
fn linkAsArchive(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
dev.check(.lld_linker);
const tracy = trace(@src());
defer tracy.end();
const comp = base.comp;
const diags = &comp.link_diags;
return linkAsArchiveInner(base, 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 as archive: {s}", .{@errorName(e)}),
};
}
fn linkAsArchiveInner(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
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) |zcu| blk: {
if (zcu.llvm_object == null) {
try base.flushZcu(arena, tid, prog_node);
} else {
// `Compilation.flush` has already made LLVM emit this object file for us.
}
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: ?Path = if (comp.compiler_rt_strat == .obj)
comp.compiler_rt_obj.?.full_object_path
else
null;
const ubsan_rt_path: ?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 (!base.disable_lld_caching) man.deinit();
const link_inputs = comp.link_inputs;
var digest: [Cache.hex_digest_len]u8 = undefined;
if (!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.
base.releaseLock();
try 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 (!base.disable_lld_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();
}
}
pub const Tag = enum {
coff,
elf,
@@ -1270,6 +1098,7 @@ pub const File = struct {
plan9,
goff,
xcoff,
lld,
pub fn Type(comptime tag: Tag) type {
return switch (tag) {
@@ -1282,10 +1111,11 @@ pub const File = struct {
.plan9 => Plan9,
.goff => Goff,
.xcoff => Xcoff,
.lld => Lld,
};
}
pub fn fromObjectFormat(ofmt: std.Target.ObjectFormat) Tag {
fn fromObjectFormat(ofmt: std.Target.ObjectFormat) Tag {
return switch (ofmt) {
.coff => .coff,
.elf => .elf,
@@ -1313,15 +1143,7 @@ pub const File = struct {
ty: InternPool.Index,
};
pub fn effectiveOutputMode(
use_lld: bool,
output_mode: std.builtin.OutputMode,
) std.builtin.OutputMode {
return if (use_lld) .Obj else output_mode;
}
pub fn determineMode(
use_lld: bool,
output_mode: std.builtin.OutputMode,
link_mode: std.builtin.LinkMode,
) fs.File.Mode {
@@ -1330,7 +1152,7 @@ pub const File = struct {
// more leniently. As another data point, C's fopen seems to open files with the
// 666 mode.
const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777;
switch (effectiveOutputMode(use_lld, output_mode)) {
switch (output_mode) {
.Lib => return switch (link_mode) {
.dynamic => executable_mode,
.static => fs.File.default_mode,
@@ -1378,6 +1200,7 @@ pub const File = struct {
return base.comp.zcu.?.codegenFail(nav_index, format, args);
}
pub const Lld = @import("link/Lld.zig");
pub const C = @import("link/C.zig");
pub const Coff = @import("link/Coff.zig");
pub const Plan9 = @import("link/Plan9.zig");
@@ -1685,116 +1508,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
}
}
pub 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});
}
/// Provided by the CLI, processed into `LinkInput` instances at the start of
/// the compilation pipeline.
pub const UnresolvedInput = union(enum) {

View File

@@ -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());

View File

@@ -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;

View File

@@ -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;

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -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;

2148
src/link/Lld.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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
@@ -341,15 +340,6 @@ pub fn flush(
arena: Allocator,
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)}),
};

View File

@@ -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];

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -867,9 +867,9 @@ fn buildOutputType(
var linker_allow_undefined_version: bool = false;
var linker_enable_new_dtags: ?bool = null;
var disable_c_depfile = false;
var linker_sort_section: ?link.File.Elf.SortSection = null;
var linker_sort_section: ?link.File.Lld.Elf.SortSection = null;
var linker_gc_sections: ?bool = null;
var linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null;
var linker_compress_debug_sections: ?link.File.Lld.Elf.CompressDebugSections = null;
var linker_allow_shlib_undefined: ?bool = null;
var allow_so_scripts: bool = false;
var linker_bind_global_refs_locally: ?bool = null;
@@ -921,7 +921,7 @@ fn buildOutputType(
var debug_compiler_runtime_libs = false;
var opt_incremental: ?bool = null;
var install_name: ?[]const u8 = null;
var hash_style: link.File.Elf.HashStyle = .both;
var hash_style: link.File.Lld.Elf.HashStyle = .both;
var entitlements: ?[]const u8 = null;
var pagezero_size: ?u64 = null;
var lib_search_strategy: link.UnresolvedInput.SearchStrategy = .paths_first;
@@ -1196,11 +1196,11 @@ fn buildOutputType(
install_name = args_iter.nextOrFatal();
} else if (mem.startsWith(u8, arg, "--compress-debug-sections=")) {
const param = arg["--compress-debug-sections=".len..];
linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, param) orelse {
linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, param) orelse {
fatal("expected --compress-debug-sections=[none|zlib|zstd], found '{s}'", .{param});
};
} else if (mem.eql(u8, arg, "--compress-debug-sections")) {
linker_compress_debug_sections = link.File.Elf.CompressDebugSections.zlib;
linker_compress_debug_sections = link.File.Lld.Elf.CompressDebugSections.zlib;
} else if (mem.eql(u8, arg, "-pagezero_size")) {
const next_arg = args_iter.nextOrFatal();
pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
@@ -2368,7 +2368,7 @@ fn buildOutputType(
if (it.only_arg.len == 0) {
linker_compress_debug_sections = .zlib;
} else {
linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, it.only_arg) orelse {
linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, it.only_arg) orelse {
fatal("expected [none|zlib|zstd] after --compress-debug-sections, found '{s}'", .{it.only_arg});
};
}
@@ -2505,7 +2505,7 @@ fn buildOutputType(
linker_print_map = true;
} else if (mem.eql(u8, arg, "--sort-section")) {
const arg1 = linker_args_it.nextOrFatal();
linker_sort_section = std.meta.stringToEnum(link.File.Elf.SortSection, arg1) orelse {
linker_sort_section = std.meta.stringToEnum(link.File.Lld.Elf.SortSection, arg1) orelse {
fatal("expected [name|alignment] after --sort-section, found '{s}'", .{arg1});
};
} else if (mem.eql(u8, arg, "--allow-shlib-undefined") or
@@ -2551,7 +2551,7 @@ fn buildOutputType(
try linker_export_symbol_names.append(arena, linker_args_it.nextOrFatal());
} else if (mem.eql(u8, arg, "--compress-debug-sections")) {
const arg1 = linker_args_it.nextOrFatal();
linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, arg1) orelse {
linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, arg1) orelse {
fatal("expected [none|zlib|zstd] after --compress-debug-sections, found '{s}'", .{arg1});
};
} else if (mem.startsWith(u8, arg, "-z")) {
@@ -2764,7 +2764,7 @@ fn buildOutputType(
mem.eql(u8, arg, "--hash-style"))
{
const next_arg = linker_args_it.nextOrFatal();
hash_style = std.meta.stringToEnum(link.File.Elf.HashStyle, next_arg) orelse {
hash_style = std.meta.stringToEnum(link.File.Lld.Elf.HashStyle, next_arg) orelse {
fatal("expected [sysv|gnu|both] after --hash-style, found '{s}'", .{
next_arg,
});