rework linker inputs

* Compilation.objects changes to Compilation.link_inputs which stores
  objects, archives, windows resources, shared objects, and strings
  intended to be put directly into the dynamic section. Order is now
  preserved between all of these kinds of linker inputs. If it is
  determined the order does not matter for a particular kind of linker
  input, that item should be moved to a different array.
* rename system_libs to windows_libs
* untangle library lookup from CLI types
* when doing library lookup, instead of using access syscalls, go ahead
  and open the files and keep the handles around for passing to the
  cache system and the linker.
* during library lookup and cache file hashing, use positioned reads to
  avoid affecting the file seek position.
* library directories are opened in the CLI and converted to Directory
  objects, warnings emitted for those that cannot be opened.
This commit is contained in:
Andrew Kelley 2024-10-16 12:14:19 -07:00
parent 4706ec81d4
commit e567abb339
12 changed files with 1517 additions and 1091 deletions

View file

@ -142,6 +142,9 @@ pub const hasher_init: Hasher = Hasher.init(&[_]u8{
pub const File = struct {
prefixed_path: PrefixedPath,
max_file_size: ?usize,
/// Populated if the user calls `addOpenedFile`.
/// The handle is not owned here.
handle: ?fs.File,
stat: Stat,
bin_digest: BinDigest,
contents: ?[]const u8,
@ -173,6 +176,11 @@ pub const File = struct {
const new = new_max_size orelse return;
file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new;
}
pub fn updateHandle(file: *File, new_handle: ?fs.File) void {
const handle = new_handle orelse return;
file.handle = handle;
}
};
pub const HashHelper = struct {
@ -363,15 +371,20 @@ pub const Manifest = struct {
/// var file_contents = cache_hash.files.keys()[file_index].contents.?;
/// ```
pub fn addFilePath(m: *Manifest, file_path: Path, max_file_size: ?usize) !usize {
return addOpenedFile(m, file_path, null, max_file_size);
}
/// Same as `addFilePath` except the file has already been opened.
pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?fs.File, max_file_size: ?usize) !usize {
const gpa = m.cache.gpa;
try m.files.ensureUnusedCapacity(gpa, 1);
const resolved_path = try fs.path.resolve(gpa, &.{
file_path.root_dir.path orelse ".",
file_path.subPathOrDot(),
path.root_dir.path orelse ".",
path.subPathOrDot(),
});
errdefer gpa.free(resolved_path);
const prefixed_path = try m.cache.findPrefixResolved(resolved_path);
return addFileInner(m, prefixed_path, max_file_size);
return addFileInner(m, prefixed_path, handle, max_file_size);
}
/// Deprecated; use `addFilePath`.
@ -383,13 +396,14 @@ pub const Manifest = struct {
const prefixed_path = try self.cache.findPrefix(file_path);
errdefer gpa.free(prefixed_path.sub_path);
return addFileInner(self, prefixed_path, max_file_size);
return addFileInner(self, prefixed_path, null, max_file_size);
}
fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, max_file_size: ?usize) !usize {
fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?fs.File, max_file_size: ?usize) usize {
const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
if (gop.found_existing) {
gop.key_ptr.updateMaxSize(max_file_size);
gop.key_ptr.updateHandle(handle);
return gop.index;
}
gop.key_ptr.* = .{
@ -398,6 +412,7 @@ pub const Manifest = struct {
.max_file_size = max_file_size,
.stat = undefined,
.bin_digest = undefined,
.handle = handle,
};
self.hash.add(prefixed_path.prefix);
@ -565,6 +580,7 @@ pub const Manifest = struct {
},
.contents = null,
.max_file_size = null,
.handle = null,
.stat = .{
.size = stat_size,
.inode = stat_inode,
@ -708,12 +724,19 @@ pub const Manifest = struct {
}
fn populateFileHash(self: *Manifest, ch_file: *File) !void {
const pp = ch_file.prefixed_path;
const dir = self.cache.prefixes()[pp.prefix].handle;
const file = try dir.openFile(pp.sub_path, .{});
defer file.close();
if (ch_file.handle) |handle| {
return populateFileHashHandle(self, ch_file, handle);
} else {
const pp = ch_file.prefixed_path;
const dir = self.cache.prefixes()[pp.prefix].handle;
const handle = try dir.openFile(pp.sub_path, .{});
defer handle.close();
return populateFileHashHandle(self, ch_file, handle);
}
}
const actual_stat = try file.stat();
fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void {
const actual_stat = try handle.stat();
ch_file.stat = .{
.size = actual_stat.size,
.mtime = actual_stat.mtime,
@ -739,8 +762,7 @@ pub const Manifest = struct {
var hasher = hasher_init;
var off: usize = 0;
while (true) {
// give me everything you've got, captain
const bytes_read = try file.read(contents[off..]);
const bytes_read = try handle.pread(contents[off..], off);
if (bytes_read == 0) break;
hasher.update(contents[off..][0..bytes_read]);
off += bytes_read;
@ -749,7 +771,7 @@ pub const Manifest = struct {
ch_file.contents = contents;
} else {
try hashFile(file, &ch_file.bin_digest);
try hashFile(handle, &ch_file.bin_digest);
}
self.hash.hasher.update(&ch_file.bin_digest);
@ -813,6 +835,7 @@ pub const Manifest = struct {
gop.key_ptr.* = .{
.prefixed_path = prefixed_path,
.max_file_size = null,
.handle = null,
.stat = undefined,
.bin_digest = undefined,
.contents = null,
@ -851,6 +874,7 @@ pub const Manifest = struct {
new_file.* = .{
.prefixed_path = prefixed_path,
.max_file_size = null,
.handle = null,
.stat = stat,
.bin_digest = undefined,
.contents = null,
@ -1067,6 +1091,7 @@ pub const Manifest = struct {
gop.key_ptr.* = .{
.prefixed_path = prefixed_path,
.max_file_size = file.max_file_size,
.handle = file.handle,
.stat = file.stat,
.bin_digest = file.bin_digest,
.contents = null,
@ -1103,14 +1128,14 @@ pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void
fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void {
var buf: [1024]u8 = undefined;
var hasher = hasher_init;
var off: u64 = 0;
while (true) {
const bytes_read = try file.read(&buf);
const bytes_read = try file.pread(&buf, off);
if (bytes_read == 0) break;
hasher.update(buf[0..bytes_read]);
off += bytes_read;
}
hasher.final(bin_digest);
}

View file

@ -76,12 +76,13 @@ implib_emit: ?Path,
docs_emit: ?Path,
root_name: [:0]const u8,
include_compiler_rt: bool,
objects: []Compilation.LinkObject,
/// Resolved into known paths, any GNU ld scripts already resolved.
link_inputs: []const link.Input,
/// Needed only for passing -F args to clang.
framework_dirs: []const []const u8,
/// These are *always* dynamically linked. Static libraries will be
/// provided as positional arguments.
system_libs: std.StringArrayHashMapUnmanaged(SystemLib),
/// These are only for DLLs dependencies fulfilled by the `.def` files shipped
/// with Zig. Static libraries are provided as `link.Input` values.
windows_libs: std.StringArrayHashMapUnmanaged(void),
version: ?std.SemanticVersion,
libc_installation: ?*const LibCInstallation,
skip_linker_dependencies: bool,
@ -384,7 +385,7 @@ const Job = union(enum) {
/// one of WASI libc static objects
wasi_libc_crt_file: wasi_libc.CrtFile,
/// The value is the index into `system_libs`.
/// The value is the index into `windows_libs`.
windows_import_lib: usize,
const Tag = @typeInfo(Job).@"union".tag_type.?;
@ -999,25 +1000,6 @@ const CacheUse = union(CacheMode) {
}
};
pub const LinkObject = struct {
path: Path,
must_link: bool = false,
needed: bool = false,
weak: bool = false,
/// When the library is passed via a positional argument, it will be
/// added as a full path. If it's `-l<lib>`, then just the basename.
///
/// Consistent with `withLOption` variable name in lld ELF driver.
loption: bool = false,
pub fn isObject(lo: LinkObject) bool {
return switch (classifyFileExt(lo.path.sub_path)) {
.object => true,
else => false,
};
}
};
pub const CreateOptions = struct {
zig_lib_directory: Directory,
local_cache_directory: Directory,
@ -1065,18 +1047,17 @@ pub const CreateOptions = struct {
/// This field is intended to be removed.
/// The ELF implementation no longer uses this data, however the MachO and COFF
/// implementations still do.
lib_dirs: []const []const u8 = &[0][]const u8{},
lib_directories: []const Directory = &.{},
rpath_list: []const []const u8 = &[0][]const u8{},
symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .empty,
c_source_files: []const CSourceFile = &.{},
rc_source_files: []const RcSourceFile = &.{},
manifest_file: ?[]const u8 = null,
rc_includes: RcIncludes = .any,
link_objects: []LinkObject = &[0]LinkObject{},
link_inputs: []const link.Input = &.{},
framework_dirs: []const []const u8 = &[0][]const u8{},
frameworks: []const Framework = &.{},
system_lib_names: []const []const u8 = &.{},
system_lib_infos: []const SystemLib = &.{},
windows_lib_names: []const []const u8 = &.{},
/// These correspond to the WASI libc emulated subcomponents including:
/// * process clocks
/// * getpid
@ -1459,12 +1440,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
};
errdefer if (opt_zcu) |zcu| zcu.deinit();
var system_libs = try std.StringArrayHashMapUnmanaged(SystemLib).init(
gpa,
options.system_lib_names,
options.system_lib_infos,
);
errdefer system_libs.deinit(gpa);
var windows_libs = try std.StringArrayHashMapUnmanaged(void).init(gpa, options.windows_lib_names, &.{});
errdefer windows_libs.deinit(gpa);
comp.* = .{
.gpa = gpa,
@ -1526,11 +1503,11 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.libcxx_abi_version = options.libcxx_abi_version,
.root_name = root_name,
.sysroot = sysroot,
.system_libs = system_libs,
.windows_libs = windows_libs,
.version = options.version,
.libc_installation = libc_dirs.libc_installation,
.include_compiler_rt = include_compiler_rt,
.objects = options.link_objects,
.link_inputs = options.link_inputs,
.framework_dirs = options.framework_dirs,
.llvm_opt_bisect_limit = options.llvm_opt_bisect_limit,
.skip_linker_dependencies = options.skip_linker_dependencies,
@ -1568,7 +1545,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.z_max_page_size = options.linker_z_max_page_size,
.darwin_sdk_layout = libc_dirs.darwin_sdk_layout,
.frameworks = options.frameworks,
.lib_dirs = options.lib_dirs,
.lib_directories = options.lib_directories,
.framework_dirs = options.framework_dirs,
.rpath_list = options.rpath_list,
.symbol_wrap_set = options.symbol_wrap_set,
@ -1851,17 +1828,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
});
// When linking mingw-w64 there are some import libs we always need.
for (mingw.always_link_libs) |name| {
try comp.system_libs.put(comp.gpa, name, .{
.needed = false,
.weak = false,
.path = null,
});
}
try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len);
for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(name, {});
}
// Generate Windows import libs.
if (target.os.tag == .windows) {
const count = comp.system_libs.count();
const count = comp.windows_libs.count();
for (0..count) |i| {
try comp.queueJob(.{ .windows_import_lib = i });
}
@ -1930,7 +1902,7 @@ pub fn destroy(comp: *Compilation) void {
comp.embed_file_work_queue.deinit();
const gpa = comp.gpa;
comp.system_libs.deinit(gpa);
comp.windows_libs.deinit(gpa);
{
var it = comp.crt_files.iterator();
@ -2563,13 +2535,7 @@ fn addNonIncrementalStuffToCacheManifest(
cache_helpers.addModule(&man.hash, comp.root_mod);
}
for (comp.objects) |obj| {
_ = try man.addFilePath(obj.path, null);
man.hash.add(obj.must_link);
man.hash.add(obj.needed);
man.hash.add(obj.weak);
man.hash.add(obj.loption);
}
try link.hashInputs(man, comp.link_inputs);
for (comp.c_object_table.keys()) |key| {
_ = try man.addFile(key.src.src_path, null);
@ -2606,7 +2572,7 @@ fn addNonIncrementalStuffToCacheManifest(
man.hash.add(comp.rc_includes);
man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
man.hash.addListOfBytes(comp.framework_dirs);
try link.hashAddSystemLibs(man, comp.system_libs);
man.hash.addListOfBytes(comp.windows_libs.keys());
cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm);
cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir);
@ -2625,12 +2591,16 @@ fn addNonIncrementalStuffToCacheManifest(
man.hash.addOptional(opts.image_base);
man.hash.addOptional(opts.gc_sections);
man.hash.add(opts.emit_relocs);
man.hash.addListOfBytes(opts.lib_dirs);
const target = comp.root_mod.resolved_target.result;
if (target.ofmt == .macho or target.ofmt == .coff) {
// TODO remove this, libraries need to be resolved by the frontend. this is already
// done by ELF.
for (opts.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path);
}
man.hash.addListOfBytes(opts.rpath_list);
man.hash.addListOfBytes(opts.symbol_wrap_set.keys());
if (comp.config.link_libc) {
man.hash.add(comp.libc_installation != null);
const target = comp.root_mod.resolved_target.result;
if (comp.libc_installation) |libc_installation| {
man.hash.addOptionalBytes(libc_installation.crt_dir);
if (target.abi == .msvc or target.abi == .itanium) {
@ -3798,7 +3768,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
const named_frame = tracy.namedFrame("windows_import_lib");
defer named_frame.end();
const link_lib = comp.system_libs.keys()[index];
const link_lib = comp.windows_libs.keys()[index];
mingw.buildImportLib(comp, link_lib) catch |err| {
// TODO Surface more error details.
comp.lockAndSetMiscFailure(
@ -4711,7 +4681,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
// file and building an object we need to link them together, but with just one it should go
// directly to the output file.
const direct_o = comp.c_source_files.len == 1 and comp.zcu == null and
comp.config.output_mode == .Obj and comp.objects.len == 0;
comp.config.output_mode == .Obj and !link.anyObjectInputs(comp.link_inputs);
const o_basename_noext = if (direct_o)
comp.root_name
else
@ -6516,24 +6486,14 @@ pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void {
// then when we create a sub-Compilation for zig libc, it also tries to
// build kernel32.lib.
if (comp.skip_linker_dependencies) return;
const target = comp.root_mod.resolved_target.result;
if (target.os.tag != .windows or target.ofmt == .c) return;
// This happens when an `extern "foo"` function is referenced.
// If we haven't seen this library yet and we're targeting Windows, we need
// to queue up a work item to produce the DLL import library for this.
const gop = try comp.system_libs.getOrPut(comp.gpa, lib_name);
if (!gop.found_existing) {
gop.value_ptr.* = .{
.needed = true,
.weak = false,
.path = null,
};
const target = comp.root_mod.resolved_target.result;
if (target.os.tag == .windows and target.ofmt != .c) {
try comp.queueJob(.{
.windows_import_lib = comp.system_libs.count() - 1,
});
}
}
const gop = try comp.windows_libs.getOrPut(comp.gpa, lib_name);
if (!gop.found_existing) try comp.queueJob(.{ .windows_import_lib = comp.windows_libs.count() - 1 });
}
/// This decides the optimization mode for all zig-provided libraries, including

View file

@ -9595,7 +9595,7 @@ fn resolveGenericBody(
}
/// Given a library name, examines if the library name should end up in
/// `link.File.Options.system_libs` table (for example, libc is always
/// `link.File.Options.windows_libs` table (for example, libc is always
/// specified via dedicated flag `link_libc` instead),
/// and puts it there if it doesn't exist.
/// It also dupes the library name which can then be saved as part of the

View file

@ -12,6 +12,7 @@ const Air = @import("Air.zig");
const Allocator = std.mem.Allocator;
const Cache = std.Build.Cache;
const Path = std.Build.Cache.Path;
const Directory = std.Build.Cache.Directory;
const Compilation = @import("Compilation.zig");
const LibCInstallation = std.zig.LibCInstallation;
const Liveness = @import("Liveness.zig");
@ -26,19 +27,6 @@ const dev = @import("dev.zig");
pub const LdScript = @import("link/LdScript.zig");
/// When adding a new field, remember to update `hashAddSystemLibs`.
/// These are *always* dynamically linked. Static libraries will be
/// provided as positional arguments.
pub const SystemLib = struct {
needed: bool,
weak: bool,
/// This can be null in two cases right now:
/// 1. Windows DLLs that zig ships such as advapi32.
/// 2. extern "foo" fn declarations where we find out about libraries too late
/// TODO: make this non-optional and resolve those two cases somehow.
path: ?Path,
};
pub const Diags = struct {
/// Stored here so that function definitions can distinguish between
/// needing an allocator for things besides error reporting.
@ -355,19 +343,6 @@ pub const Diags = struct {
}
};
pub fn hashAddSystemLibs(
man: *Cache.Manifest,
hm: std.StringArrayHashMapUnmanaged(SystemLib),
) !void {
const keys = hm.keys();
man.hash.addListOfBytes(keys);
for (hm.values()) |value| {
man.hash.add(value.needed);
man.hash.add(value.weak);
if (value.path) |p| _ = try man.addFilePath(p, null);
}
}
pub const producer_string = if (builtin.is_test) "zig test" else "zig " ++ build_options.version;
pub const File = struct {
@ -455,7 +430,7 @@ pub const File = struct {
compatibility_version: ?std.SemanticVersion,
// TODO: remove this. libraries are resolved by the frontend.
lib_dirs: []const []const u8,
lib_directories: []const Directory,
framework_dirs: []const []const u8,
rpath_list: []const []const u8,
@ -1027,7 +1002,6 @@ pub const File = struct {
defer tracy.end();
const comp = base.comp;
const gpa = comp.gpa;
const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
@ -1059,7 +1033,7 @@ pub const File = struct {
var man: Cache.Manifest = undefined;
defer if (!base.disable_lld_caching) man.deinit();
const objects = comp.objects;
const link_inputs = comp.link_inputs;
var digest: [Cache.hex_digest_len]u8 = undefined;
@ -1069,11 +1043,8 @@ pub const File = struct {
// We are about to obtain this lock, so here we give other processes a chance first.
base.releaseLock();
for (objects) |obj| {
_ = try man.addFilePath(obj.path, null);
man.hash.add(obj.must_link);
man.hash.add(obj.loption);
}
try hashInputs(&man, link_inputs);
for (comp.c_object_table.keys()) |key| {
_ = try man.addFilePath(key.status.success.object_path, null);
}
@ -1109,26 +1080,24 @@ pub const File = struct {
};
}
const win32_resource_table_len = comp.win32_resource_table.count();
const num_object_files = objects.len + comp.c_object_table.count() + win32_resource_table_len + 2;
var object_files = try std.ArrayList([*:0]const u8).initCapacity(gpa, num_object_files);
defer object_files.deinit();
var object_files: std.ArrayListUnmanaged([*:0]const u8) = .empty;
for (objects) |obj| {
object_files.appendAssumeCapacity(try obj.path.toStringZ(arena));
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 (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 (comp.verbose_link) {
std.debug.print("ar rcs {s}", .{full_out_path_z});
@ -1404,3 +1373,676 @@ pub fn spawnLld(
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) {
/// A library name that could potentially be dynamic or static depending on
/// query parameters, resolved according to library directories.
/// This could potentially resolve to a GNU ld script, resulting in more
/// library dependencies.
name_query: NameQuery,
/// When a file path is provided, query info is still needed because the
/// path may point to a .so file which may actually be a GNU ld script that
/// references library names which need to be resolved.
path_query: PathQuery,
/// Strings that come from GNU ld scripts. Is it a filename? Is it a path?
/// Who knows! Fuck around and find out.
ambiguous_name: NameQuery,
/// Put exactly this string in the dynamic section, no rpath.
dso_exact: Input.DsoExact,
///// Relocatable.
//object: Input.Object,
///// Static library.
//archive: Input.Object,
///// Windows resource file.
//winres: Path,
pub const NameQuery = struct {
name: []const u8,
query: Query,
};
pub const PathQuery = struct {
path: Path,
query: Query,
};
pub const Query = struct {
needed: bool = false,
weak: bool = false,
reexport: bool = false,
must_link: bool = false,
hidden: bool = false,
allow_so_scripts: bool = false,
preferred_mode: std.builtin.LinkMode,
search_strategy: SearchStrategy,
fn fallbackMode(q: Query) std.builtin.LinkMode {
assert(q.search_strategy != .no_fallback);
return switch (q.preferred_mode) {
.dynamic => .static,
.static => .dynamic,
};
}
};
pub const SearchStrategy = enum {
paths_first,
mode_first,
no_fallback,
};
};
pub const Input = union(enum) {
object: Object,
archive: Object,
res: Res,
/// May not be a GNU ld script. Those are resolved when converting from
/// `UnresolvedInput` to `Input` values.
dso: Dso,
dso_exact: DsoExact,
pub const Object = struct {
path: Path,
file: fs.File,
must_link: bool,
hidden: bool,
};
pub const Res = struct {
path: Path,
file: fs.File,
};
pub const Dso = struct {
path: Path,
file: fs.File,
needed: bool,
weak: bool,
reexport: bool,
};
pub const DsoExact = struct {
/// Includes the ":" prefix. This is intended to be put into the DSO
/// section verbatim with no corresponding rpaths.
name: []const u8,
};
/// Returns `null` in the case of `dso_exact`.
pub fn path(input: Input) ?Path {
return switch (input) {
.object, .archive => |obj| obj.path,
inline .res, .dso => |x| x.path,
.dso_exact => null,
};
}
/// Returns `null` in the case of `dso_exact`.
pub fn pathAndFile(input: Input) ?struct { Path, fs.File } {
return switch (input) {
.object, .archive => |obj| .{ obj.path, obj.file },
inline .res, .dso => |x| .{ x.path, x.file },
.dso_exact => null,
};
}
};
pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
for (link_inputs) |link_input| {
man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input));
switch (link_input) {
.object, .archive => |obj| {
_ = try man.addOpenedFile(obj.path, obj.file, null);
man.hash.add(obj.must_link);
man.hash.add(obj.hidden);
},
.res => |res| {
_ = try man.addOpenedFile(res.path, res.file, null);
},
.dso => |dso| {
_ = try man.addOpenedFile(dso.path, dso.file, null);
man.hash.add(dso.needed);
man.hash.add(dso.weak);
man.hash.add(dso.reexport);
},
.dso_exact => |dso_exact| {
man.hash.addBytes(dso_exact.name);
},
}
}
}
pub fn resolveInputs(
gpa: Allocator,
arena: Allocator,
target: std.Target,
/// This function mutates this array but does not take ownership.
/// Allocated with `gpa`.
unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput),
/// Allocated with `gpa`.
resolved_inputs: *std.ArrayListUnmanaged(Input),
lib_directories: []const Cache.Directory,
color: std.zig.Color,
) Allocator.Error!void {
var checked_paths: std.ArrayListUnmanaged(u8) = .empty;
defer checked_paths.deinit(gpa);
var ld_script_bytes: std.ArrayListUnmanaged(u8) = .empty;
defer ld_script_bytes.deinit(gpa);
var failed_libs: std.ArrayListUnmanaged(struct {
name: []const u8,
strategy: UnresolvedInput.SearchStrategy,
checked_paths: []const u8,
preferred_mode: std.builtin.LinkMode,
}) = .empty;
// Convert external system libs into a stack so that items can be
// pushed to it.
//
// This is necessary because shared objects might turn out to be
// "linker scripts" that in fact resolve to one or more other
// external system libs, including parameters such as "needed".
//
// Unfortunately, such files need to be detected immediately, so
// that this library search logic can be applied to them.
mem.reverse(UnresolvedInput, unresolved_inputs.items);
syslib: while (unresolved_inputs.popOrNull()) |unresolved_input| {
const name_query: UnresolvedInput.NameQuery = switch (unresolved_input) {
.name_query => |nq| nq,
.ambiguous_name => |an| an: {
const lib_name, const link_mode = stripLibPrefixAndSuffix(an.name, target) orelse {
try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, .{
.path = Path.initCwd(an.name),
.query = an.query,
}, color);
continue;
};
break :an .{
.name = lib_name,
.query = .{
.needed = an.query.needed,
.weak = an.query.weak,
.reexport = an.query.reexport,
.must_link = an.query.must_link,
.hidden = an.query.hidden,
.preferred_mode = link_mode,
.search_strategy = .no_fallback,
},
};
},
.path_query => |pq| {
try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, pq, color);
continue;
},
.dso_exact => |dso_exact| {
try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact });
continue;
},
};
const query = name_query.query;
// Checked in the first pass above while looking for libc libraries.
assert(!fs.path.isAbsolute(name_query.name));
checked_paths.clearRetainingCapacity();
switch (query.search_strategy) {
.mode_first, .no_fallback => {
// check for preferred mode
for (lib_directories) |lib_directory| switch (try resolveLibInput(
gpa,
arena,
unresolved_inputs,
resolved_inputs,
&checked_paths,
&ld_script_bytes,
lib_directory,
name_query,
target,
query.preferred_mode,
color,
)) {
.ok => continue :syslib,
.no_match => {},
};
// check for fallback mode
if (query.search_strategy == .no_fallback) {
try failed_libs.append(arena, .{
.name = name_query.name,
.strategy = query.search_strategy,
.checked_paths = try arena.dupe(u8, checked_paths.items),
.preferred_mode = query.preferred_mode,
});
continue :syslib;
}
for (lib_directories) |lib_directory| switch (try resolveLibInput(
gpa,
arena,
unresolved_inputs,
resolved_inputs,
&checked_paths,
&ld_script_bytes,
lib_directory,
name_query,
target,
query.fallbackMode(),
color,
)) {
.ok => continue :syslib,
.no_match => {},
};
try failed_libs.append(arena, .{
.name = name_query.name,
.strategy = query.search_strategy,
.checked_paths = try arena.dupe(u8, checked_paths.items),
.preferred_mode = query.preferred_mode,
});
continue :syslib;
},
.paths_first => {
for (lib_directories) |lib_directory| {
// check for preferred mode
switch (try resolveLibInput(
gpa,
arena,
unresolved_inputs,
resolved_inputs,
&checked_paths,
&ld_script_bytes,
lib_directory,
name_query,
target,
query.preferred_mode,
color,
)) {
.ok => continue :syslib,
.no_match => {},
}
// check for fallback mode
switch (try resolveLibInput(
gpa,
arena,
unresolved_inputs,
resolved_inputs,
&checked_paths,
&ld_script_bytes,
lib_directory,
name_query,
target,
query.fallbackMode(),
color,
)) {
.ok => continue :syslib,
.no_match => {},
}
}
try failed_libs.append(arena, .{
.name = name_query.name,
.strategy = query.search_strategy,
.checked_paths = try arena.dupe(u8, checked_paths.items),
.preferred_mode = query.preferred_mode,
});
continue :syslib;
},
}
@compileError("unreachable");
}
if (failed_libs.items.len > 0) {
for (failed_libs.items) |f| {
const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths;
std.log.err("unable to find {s} system library '{s}' using strategy '{s}'. searched paths:{s}", .{
@tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths,
});
}
std.process.exit(1);
}
}
const AccessLibPathResult = enum { ok, no_match };
const fatal = std.process.fatal;
fn resolveLibInput(
gpa: Allocator,
arena: Allocator,
/// Allocated via `gpa`.
unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput),
/// Allocated via `gpa`.
resolved_inputs: *std.ArrayListUnmanaged(Input),
/// Allocated via `gpa`.
checked_paths: *std.ArrayListUnmanaged(u8),
/// Allocated via `gpa`.
ld_script_bytes: *std.ArrayListUnmanaged(u8),
lib_directory: Directory,
name_query: UnresolvedInput.NameQuery,
target: std.Target,
link_mode: std.builtin.LinkMode,
color: std.zig.Color,
) Allocator.Error!AccessLibPathResult {
try resolved_inputs.ensureUnusedCapacity(gpa, 1);
const lib_name = name_query.name;
if (target.isDarwin() and link_mode == .dynamic) tbd: {
// Prefer .tbd over .dylib.
const test_path: Path = .{
.root_dir = lib_directory,
.sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}),
};
try checked_paths.writer(gpa).print("\n {}", .{test_path});
var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => break :tbd,
else => |e| fatal("unable to search for tbd library '{}': {s}", .{ test_path, @errorName(e) }),
};
errdefer file.close();
return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, name_query.query);
}
{
const test_path: Path = .{
.root_dir = lib_directory,
.sub_path = try std.fmt.allocPrint(arena, "{s}{s}{s}", .{
target.libPrefix(), lib_name, switch (link_mode) {
.static => target.staticLibSuffix(),
.dynamic => target.dynamicLibSuffix(),
},
}),
};
try checked_paths.writer(gpa).print("\n {}", .{test_path});
switch (try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{
.path = test_path,
.query = name_query.query,
}, link_mode, color)) {
.no_match => {},
.ok => return .ok,
}
}
// In the case of Darwin, the main check will be .dylib, so here we
// additionally check for .so files.
if (target.isDarwin() and link_mode == .dynamic) so: {
const test_path: Path = .{
.root_dir = lib_directory,
.sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),
};
try checked_paths.writer(gpa).print("\n {}", .{test_path});
var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => break :so,
else => |e| fatal("unable to search for so library '{}': {s}", .{
test_path, @errorName(e),
}),
};
errdefer file.close();
return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, name_query.query);
}
// In the case of MinGW, the main check will be .lib but we also need to
// look for `libfoo.a`.
if (target.isMinGW() and link_mode == .static) mingw: {
const test_path: Path = .{
.root_dir = lib_directory,
.sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),
};
try checked_paths.writer(gpa).print("\n {}", .{test_path});
var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => break :mingw,
else => |e| fatal("unable to search for static library '{}': {s}", .{ test_path, @errorName(e) }),
};
errdefer file.close();
return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, name_query.query);
}
return .no_match;
}
fn finishAccessLibPath(
resolved_inputs: *std.ArrayListUnmanaged(Input),
path: Path,
file: std.fs.File,
link_mode: std.builtin.LinkMode,
query: UnresolvedInput.Query,
) AccessLibPathResult {
switch (link_mode) {
.static => resolved_inputs.appendAssumeCapacity(.{ .archive = .{
.path = path,
.file = file,
.must_link = query.must_link,
.hidden = query.hidden,
} }),
.dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{
.path = path,
.file = file,
.needed = query.needed,
.weak = query.weak,
.reexport = query.reexport,
} }),
}
return .ok;
}
fn resolvePathInput(
gpa: Allocator,
arena: Allocator,
/// Allocated with `gpa`.
unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput),
/// Allocated with `gpa`.
resolved_inputs: *std.ArrayListUnmanaged(Input),
/// Allocated via `gpa`.
ld_script_bytes: *std.ArrayListUnmanaged(u8),
target: std.Target,
pq: UnresolvedInput.PathQuery,
color: std.zig.Color,
) Allocator.Error!void {
switch (switch (Compilation.classifyFileExt(pq.path.sub_path)) {
.static_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color),
.shared_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
.object => {
var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
fatal("failed to open object {}: {s}", .{ pq.path, @errorName(err) });
errdefer file.close();
try resolved_inputs.append(gpa, .{ .object = .{
.path = pq.path,
.file = file,
.must_link = pq.query.must_link,
.hidden = pq.query.hidden,
} });
return;
},
.res => {
var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
fatal("failed to open windows resource {}: {s}", .{ pq.path, @errorName(err) });
errdefer file.close();
try resolved_inputs.append(gpa, .{ .res = .{
.path = pq.path,
.file = file,
} });
return;
},
else => fatal("{}: unrecognized file extension", .{pq.path}),
}) {
.ok => {},
.no_match => fatal("{}: file not found", .{pq.path}),
}
}
fn resolvePathInputLib(
gpa: Allocator,
arena: Allocator,
/// Allocated with `gpa`.
unresolved_inputs: *std.ArrayListUnmanaged(UnresolvedInput),
/// Allocated with `gpa`.
resolved_inputs: *std.ArrayListUnmanaged(Input),
/// Allocated via `gpa`.
ld_script_bytes: *std.ArrayListUnmanaged(u8),
target: std.Target,
pq: UnresolvedInput.PathQuery,
link_mode: std.builtin.LinkMode,
color: std.zig.Color,
) Allocator.Error!AccessLibPathResult {
const test_path: Path = pq.path;
// In the case of .so files, they might actually be "linker scripts"
// that contain references to other libraries.
if (pq.query.allow_so_scripts and target.ofmt == .elf and mem.endsWith(u8, test_path.sub_path, ".so")) {
var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => return .no_match,
else => |e| fatal("unable to search for {s} library '{'}': {s}", .{
@tagName(link_mode), test_path, @errorName(e),
}),
};
errdefer file.close();
try ld_script_bytes.resize(gpa, @sizeOf(std.elf.Elf64_Ehdr));
const n = file.preadAll(ld_script_bytes.items, 0) catch |err| fatal("failed to read '{'}': {s}", .{
test_path, @errorName(err),
});
elf_file: {
if (n != ld_script_bytes.items.len) break :elf_file;
if (!mem.eql(u8, ld_script_bytes.items[0..4], "\x7fELF")) break :elf_file;
// Appears to be an ELF file.
return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, pq.query);
}
const stat = file.stat() catch |err|
fatal("failed to stat {}: {s}", .{ test_path, @errorName(err) });
const size = std.math.cast(u32, stat.size) orelse
fatal("{}: linker script too big", .{test_path});
try ld_script_bytes.resize(gpa, size);
const buf = ld_script_bytes.items[n..];
const n2 = file.preadAll(buf, n) catch |err|
fatal("failed to read {}: {s}", .{ test_path, @errorName(err) });
if (n2 != buf.len) fatal("failed to read {}: unexpected end of file", .{test_path});
var diags = Diags.init(gpa);
defer diags.deinit();
const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items);
if (diags.hasErrors()) {
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
try wip_errors.init(gpa);
defer wip_errors.deinit();
try diags.addMessagesToBundle(&wip_errors);
var error_bundle = try wip_errors.toOwnedBundle("");
defer error_bundle.deinit(gpa);
error_bundle.renderToStdErr(color.renderOptions());
std.process.exit(1);
}
var ld_script = ld_script_result catch |err|
fatal("{}: failed to parse linker script: {s}", .{ test_path, @errorName(err) });
defer ld_script.deinit(gpa);
try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len);
for (ld_script.args) |arg| {
const query: UnresolvedInput.Query = .{
.needed = arg.needed or pq.query.needed,
.weak = pq.query.weak,
.reexport = pq.query.reexport,
.preferred_mode = pq.query.preferred_mode,
.search_strategy = pq.query.search_strategy,
.allow_so_scripts = pq.query.allow_so_scripts,
};
if (mem.startsWith(u8, arg.path, "-l")) {
unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{
.name = try arena.dupe(u8, arg.path["-l".len..]),
.query = query,
} });
} else {
unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{
.name = try arena.dupe(u8, arg.path),
.query = query,
} });
}
}
file.close();
return .ok;
}
var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => return .no_match,
else => |e| fatal("unable to search for {s} library {}: {s}", .{
@tagName(link_mode), test_path, @errorName(e),
}),
};
errdefer file.close();
return finishAccessLibPath(resolved_inputs, test_path, file, link_mode, pq.query);
}
pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object {
var file = try path.root_dir.handle.openFile(path.sub_path, .{});
errdefer file.close();
return .{
.path = path,
.file = file,
.must_link = must_link,
.hidden = hidden,
};
}
pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso {
var file = try path.root_dir.handle.openFile(path.sub_path, .{});
errdefer file.close();
return .{
.path = path,
.file = file,
.needed = needed,
.weak = weak,
.reexport = reexport,
};
}
pub fn openObjectInput(diags: *Diags, path: Path) error{LinkFailure}!Input {
return .{ .object = openObject(path, false, false) catch |err| {
return diags.failParse(path, "failed to open {}: {s}", .{ path, @errorName(err) });
} };
}
pub fn openArchiveInput(diags: *Diags, path: Path) error{LinkFailure}!Input {
return .{ .archive = openObject(path, false, false) catch |err| {
return diags.failParse(path, "failed to open {}: {s}", .{ path, @errorName(err) });
} };
}
fn stripLibPrefixAndSuffix(path: []const u8, target: std.Target) ?struct { []const u8, std.builtin.LinkMode } {
const prefix = target.libPrefix();
const static_suffix = target.staticLibSuffix();
const dynamic_suffix = target.dynamicLibSuffix();
const basename = fs.path.basename(path);
const unlibbed = if (mem.startsWith(u8, basename, prefix)) basename[prefix.len..] else return null;
if (mem.endsWith(u8, unlibbed, static_suffix)) return .{
unlibbed[0 .. unlibbed.len - static_suffix.len], .static,
};
if (mem.endsWith(u8, unlibbed, dynamic_suffix)) return .{
unlibbed[0 .. unlibbed.len - dynamic_suffix.len], .dynamic,
};
return null;
}
/// Returns true if and only if there is at least one input of type object,
/// archive, or Windows resource file.
pub fn anyObjectInputs(inputs: []const Input) bool {
return countObjectInputs(inputs) != 0;
}
/// Returns the number of inputs of type object, archive, or Windows resource file.
pub fn countObjectInputs(inputs: []const Input) usize {
var count: usize = 0;
for (inputs) |input| switch (input) {
.dso, .dso_exact => continue,
.res, .object, .archive => count += 1,
};
return count;
}
/// Returns the first input of type object or archive.
pub fn firstObjectInput(inputs: []const Input) ?Input.Object {
for (inputs) |input| switch (input) {
.object, .archive => |obj| return obj,
.res, .dso, .dso_exact => continue,
};
return null;
}

View file

@ -16,7 +16,7 @@ dynamicbase: bool,
/// default or populated together. They should not be separate fields.
major_subsystem_version: u16,
minor_subsystem_version: u16,
lib_dirs: []const []const u8,
lib_directories: []const Directory,
entry: link.File.OpenOptions.Entry,
entry_addr: ?u32,
module_definition_file: ?[]const u8,
@ -297,7 +297,7 @@ pub fn createEmpty(
.dynamicbase = options.dynamicbase,
.major_subsystem_version = options.major_subsystem_version orelse 6,
.minor_subsystem_version = options.minor_subsystem_version orelse 0,
.lib_dirs = options.lib_dirs,
.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,
@ -2727,6 +2727,7 @@ const mem = std.mem;
const Allocator = std.mem.Allocator;
const Path = std.Build.Cache.Path;
const Directory = std.Build.Cache.Directory;
const codegen = @import("../codegen.zig");
const link = @import("../link.zig");

View file

@ -8,6 +8,7 @@ const log = std.log.scoped(.link);
const mem = std.mem;
const Cache = std.Build.Cache;
const Path = std.Build.Cache.Path;
const Directory = std.Build.Cache.Directory;
const mingw = @import("../../mingw.zig");
const link = @import("../../link.zig");
@ -74,10 +75,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
comptime assert(Compilation.link_hash_implementation_version == 14);
for (comp.objects) |obj| {
_ = try man.addFilePath(obj.path, null);
man.hash.add(obj.must_link);
}
try link.hashInputs(&man, comp.link_inputs);
for (comp.c_object_table.keys()) |key| {
_ = try man.addFilePath(key.status.success.object_path, null);
}
@ -88,7 +86,10 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
man.hash.addOptionalBytes(entry_name);
man.hash.add(self.base.stack_size);
man.hash.add(self.image_base);
man.hash.addListOfBytes(self.lib_dirs);
{
// TODO remove this, libraries must instead be resolved by the frontend.
for (self.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);
@ -100,7 +101,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
}
}
}
try link.hashAddSystemLibs(&man, comp.system_libs);
man.hash.addListOfBytes(comp.windows_libs.keys());
man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
man.hash.addOptional(self.subsystem);
man.hash.add(comp.config.is_test);
@ -148,8 +149,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
// 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 (comp.objects.len != 0)
break :blk comp.objects[0].path;
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;
@ -266,18 +266,24 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
}
}
for (self.lib_dirs) |lib_dir| {
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir}));
for (self.lib_directories) |lib_directory| {
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_directory.path orelse "."}));
}
try argv.ensureUnusedCapacity(comp.objects.len);
for (comp.objects) |obj| {
if (obj.must_link) {
argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Path, obj.path)}));
} else {
argv.appendAssumeCapacity(try obj.path.toString(arena));
}
}
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));
@ -484,20 +490,20 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
}
try argv.ensureUnusedCapacity(comp.system_libs.count());
for (comp.system_libs.keys()) |key| {
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, self.lib_dirs)) |full_path| {
if (try findLib(arena, lib_basename, self.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, self.lib_dirs)) |full_path| {
if (try findLib(arena, fallback_name, self.lib_directories)) |full_path| {
argv.appendAssumeCapacity(full_path);
continue;
}
@ -530,14 +536,13 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
}
}
fn findLib(arena: Allocator, name: []const u8, lib_dirs: []const []const u8) !?[]const u8 {
for (lib_dirs) |lib_dir| {
const full_path = try fs.path.join(arena, &.{ lib_dir, name });
fs.cwd().access(full_path, .{}) catch |err| switch (err) {
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 full_path;
return try lib_directory.join(arena, &.{name});
}
return null;
}

View file

@ -796,44 +796,55 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
const csu = try comp.getCrtPaths(arena);
// csu prelude
if (csu.crt0) |path| parseObjectReportingFailure(self, path);
if (csu.crti) |path| parseObjectReportingFailure(self, path);
if (csu.crtbegin) |path| parseObjectReportingFailure(self, path);
if (csu.crt0) |path| openParseObjectReportingFailure(self, path);
if (csu.crti) |path| openParseObjectReportingFailure(self, path);
if (csu.crtbegin) |path| openParseObjectReportingFailure(self, path);
for (comp.objects) |obj| {
parseInputReportingFailure(self, obj.path, obj.needed, obj.must_link);
}
// objects and archives
for (comp.link_inputs) |link_input| switch (link_input) {
.object, .archive => parseInputReportingFailure(self, link_input),
.dso_exact => @panic("TODO"),
.dso => continue, // handled below
.res => unreachable,
};
// This is a set of object files emitted by clang in a single `build-exe` invocation.
// For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
// in this set.
for (comp.c_object_table.keys()) |key| {
parseObjectReportingFailure(self, key.status.success.object_path);
openParseObjectReportingFailure(self, key.status.success.object_path);
}
if (module_obj_path) |path| parseObjectReportingFailure(self, path);
if (module_obj_path) |path| openParseObjectReportingFailure(self, path);
if (comp.config.any_sanitize_thread) parseCrtFileReportingFailure(self, comp.tsan_lib.?);
if (comp.config.any_fuzz) parseCrtFileReportingFailure(self, comp.fuzzer_lib.?);
if (comp.config.any_sanitize_thread)
openParseArchiveReportingFailure(self, comp.tsan_lib.?.full_object_path);
if (comp.config.any_fuzz)
openParseArchiveReportingFailure(self, comp.fuzzer_lib.?.full_object_path);
// libc
if (!comp.skip_linker_dependencies and !comp.config.link_libc) {
if (comp.libc_static_lib) |lib| parseCrtFileReportingFailure(self, lib);
if (comp.libc_static_lib) |lib|
openParseArchiveReportingFailure(self, lib.full_object_path);
}
for (comp.system_libs.values()) |lib_info| {
parseInputReportingFailure(self, lib_info.path.?, lib_info.needed, false);
}
// dynamic libraries
for (comp.link_inputs) |link_input| switch (link_input) {
.object, .archive, .dso_exact => continue, // handled above
.dso => parseInputReportingFailure(self, link_input),
.res => unreachable,
};
// libc++ dep
if (comp.config.link_libcpp) {
parseInputReportingFailure(self, comp.libcxxabi_static_lib.?.full_object_path, false, false);
parseInputReportingFailure(self, comp.libcxx_static_lib.?.full_object_path, false, false);
openParseArchiveReportingFailure(self, comp.libcxxabi_static_lib.?.full_object_path);
openParseArchiveReportingFailure(self, comp.libcxx_static_lib.?.full_object_path);
}
// libunwind dep
if (comp.config.link_libunwind) {
parseInputReportingFailure(self, comp.libunwind_static_lib.?.full_object_path, false, false);
openParseArchiveReportingFailure(self, comp.libunwind_static_lib.?.full_object_path);
}
// libc dep
@ -853,7 +864,10 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
lc.crt_dir.?, lib_name, suffix,
});
const resolved_path = Path.initCwd(lib_path);
parseInputReportingFailure(self, resolved_path, false, false);
switch (comp.config.link_mode) {
.static => openParseArchiveReportingFailure(self, resolved_path),
.dynamic => openParseDsoReportingFailure(self, resolved_path),
}
}
} else if (target.isGnuLibC()) {
for (glibc.libs) |lib| {
@ -864,15 +878,19 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
const lib_path = Path.initCwd(try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{
comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
}));
parseInputReportingFailure(self, lib_path, false, false);
openParseDsoReportingFailure(self, lib_path);
}
parseInputReportingFailure(self, try comp.get_libc_crt_file(arena, "libc_nonshared.a"), false, false);
const crt_file_path = try comp.get_libc_crt_file(arena, "libc_nonshared.a");
openParseArchiveReportingFailure(self, crt_file_path);
} else if (target.isMusl()) {
const path = try comp.get_libc_crt_file(arena, switch (link_mode) {
.static => "libc.a",
.dynamic => "libc.so",
});
parseInputReportingFailure(self, path, false, false);
switch (link_mode) {
.static => openParseArchiveReportingFailure(self, path),
.dynamic => openParseDsoReportingFailure(self, path),
}
} else {
diags.flags.missing_libc = true;
}
@ -884,14 +902,14 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
// to be after the shared libraries, so they are picked up from the shared
// libraries, not libcompiler_rt.
if (comp.compiler_rt_lib) |crt_file| {
parseInputReportingFailure(self, crt_file.full_object_path, false, false);
openParseArchiveReportingFailure(self, crt_file.full_object_path);
} else if (comp.compiler_rt_obj) |crt_file| {
parseObjectReportingFailure(self, crt_file.full_object_path);
openParseObjectReportingFailure(self, crt_file.full_object_path);
}
// csu postlude
if (csu.crtend) |path| parseObjectReportingFailure(self, path);
if (csu.crtn) |path| parseObjectReportingFailure(self, path);
if (csu.crtend) |path| openParseObjectReportingFailure(self, path);
if (csu.crtn) |path| openParseObjectReportingFailure(self, path);
if (diags.hasErrors()) return error.FlushFailure;
@ -1087,9 +1105,15 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void {
try argv.append(full_out_path);
if (self.base.isRelocatable()) {
for (comp.objects) |obj| {
try argv.append(try obj.path.toString(arena));
}
for (self.base.comp.link_inputs) |link_input| switch (link_input) {
.res => unreachable,
.dso => |dso| try argv.append(try dso.path.toString(arena)),
.object, .archive => |obj| try argv.append(try obj.path.toString(arena)),
.dso_exact => |dso_exact| {
assert(dso_exact.name[0] == ':');
try argv.appendSlice(&.{ "-l", dso_exact.name });
},
};
for (comp.c_object_table.keys()) |key| {
try argv.append(try key.status.success.object_path.toString(arena));
@ -1186,20 +1210,26 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void {
}
var whole_archive = false;
for (comp.objects) |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;
}
if (obj.loption) {
try argv.append("-l");
}
try argv.append(try obj.path.toString(arena));
}
for (self.base.comp.link_inputs) |link_input| switch (link_input) {
.res => unreachable,
.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;
@ -1231,25 +1261,28 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void {
// Shared libraries.
// Worst-case, we need an --as-needed argument for every lib, as well
// as one before and one after.
try argv.ensureUnusedCapacity(self.base.comp.system_libs.keys().len * 2 + 2);
argv.appendAssumeCapacity("--as-needed");
var as_needed = true;
for (self.base.comp.system_libs.values()) |lib_info| {
const lib_as_needed = !lib_info.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;
},
}
argv.appendAssumeCapacity(try lib_info.path.?.toString(arena));
}
for (self.base.comp.link_inputs) |link_input| switch (link_input) {
.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 => {
try argv.append("--no-as-needed");
as_needed = false;
},
0b10 => {
try argv.append("--as-needed");
as_needed = true;
},
}
argv.appendAssumeCapacity(try dso.path.toString(arena));
},
.res => unreachable,
};
if (!as_needed) {
argv.appendAssumeCapacity("--as-needed");
@ -1321,59 +1354,51 @@ pub const ParseError = error{
UnknownFileType,
} || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError;
fn parseCrtFileReportingFailure(self: *Elf, crt_file: Compilation.CrtFile) void {
parseInputReportingFailure(self, crt_file.full_object_path, false, false);
}
pub fn parseInputReportingFailure(self: *Elf, path: Path, needed: bool, must_link: bool) void {
pub fn parseInputReportingFailure(self: *Elf, input: link.Input) void {
const gpa = self.base.comp.gpa;
const diags = &self.base.comp.link_diags;
const target = self.getTarget();
switch (Compilation.classifyFileExt(path.sub_path)) {
.object => parseObjectReportingFailure(self, path),
.shared_library => parseSharedObject(gpa, diags, .{
.path = path,
.needed = needed,
}, &self.shared_objects, &self.files, target) catch |err| switch (err) {
error.LinkFailure => return, // already reported
error.BadMagic, error.UnexpectedEndOfFile => {
var notes = diags.addErrorWithNotes(2) catch return diags.setAllocFailure();
notes.addMsg("failed to parse shared object: {s}", .{@errorName(err)}) catch return diags.setAllocFailure();
notes.addNote("while parsing {}", .{path}) catch return diags.setAllocFailure();
notes.addNote("{s}", .{@as([]const u8, "the file may be a GNU ld script, in which case it is not an ELF file but a text file referencing other libraries to link. In this case, avoid depending on the library, convince your system administrators to refrain from using this kind of file, or pass -fallow-so-scripts to force the compiler to check every shared library in case it is an ld script.")}) catch return diags.setAllocFailure();
},
else => |e| diags.addParseError(path, "failed to parse shared object: {s}", .{@errorName(e)}),
},
.static_library => parseArchive(self, path, must_link) catch |err| switch (err) {
error.LinkFailure => return, // already reported
else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
},
else => diags.addParseError(path, "unrecognized file type", .{}),
switch (input) {
.res => unreachable,
.dso_exact => unreachable,
.object => |obj| parseObjectReportingFailure(self, obj),
.archive => |obj| parseArchiveReportingFailure(self, obj),
.dso => |dso| parseDsoReportingFailure(gpa, diags, dso, &self.shared_objects, &self.files, target),
}
}
pub fn parseObjectReportingFailure(self: *Elf, path: Path) void {
pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
const diags = &self.base.comp.link_diags;
self.parseObject(path) catch |err| switch (err) {
const obj = link.openObject(path, false, false) catch |err| {
switch (diags.failParse(path, "failed to open object {}: {s}", .{ path, @errorName(err) })) {
error.LinkFailure => return,
}
};
self.parseObjectReportingFailure(obj);
}
pub fn parseObjectReportingFailure(self: *Elf, obj: link.Input.Object) void {
const diags = &self.base.comp.link_diags;
self.parseObject(obj) catch |err| switch (err) {
error.LinkFailure => return, // already reported
else => |e| diags.addParseError(path, "unable to parse object: {s}", .{@errorName(e)}),
else => |e| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}),
};
}
fn parseObject(self: *Elf, path: Path) ParseError!void {
fn parseObject(self: *Elf, obj: link.Input.Object) ParseError!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = self.base.comp.gpa;
const handle = try path.root_dir.handle.openFile(path.sub_path, .{});
const handle = obj.file;
const fh = try self.addFileHandle(handle);
const index: File.Index = @intCast(try self.files.addOne(gpa));
self.files.set(index, .{ .object = .{
.path = .{
.root_dir = path.root_dir,
.sub_path = try gpa.dupe(u8, path.sub_path),
.root_dir = obj.path.root_dir,
.sub_path = try gpa.dupe(u8, obj.path.sub_path),
},
.file_handle = fh,
.index = index,
@ -1384,17 +1409,35 @@ fn parseObject(self: *Elf, path: Path) ParseError!void {
try object.parse(self);
}
fn parseArchive(self: *Elf, path: Path, must_link: bool) ParseError!void {
pub fn openParseArchiveReportingFailure(self: *Elf, path: Path) void {
const diags = &self.base.comp.link_diags;
const obj = link.openObject(path, false, false) catch |err| {
switch (diags.failParse(path, "failed to open archive {}: {s}", .{ path, @errorName(err) })) {
error.LinkFailure => return,
}
};
parseArchiveReportingFailure(self, obj);
}
pub fn parseArchiveReportingFailure(self: *Elf, obj: link.Input.Object) void {
const diags = &self.base.comp.link_diags;
self.parseArchive(obj) catch |err| switch (err) {
error.LinkFailure => return, // already reported
else => |e| diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}),
};
}
fn parseArchive(self: *Elf, obj: link.Input.Object) ParseError!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = self.base.comp.gpa;
const handle = try path.root_dir.handle.openFile(path.sub_path, .{});
const handle = obj.file;
const fh = try self.addFileHandle(handle);
var archive: Archive = .{};
defer archive.deinit(gpa);
try archive.parse(self, path, fh);
try archive.parse(self, obj.path, fh);
const objects = try archive.objects.toOwnedSlice(gpa);
defer gpa.free(objects);
@ -1404,16 +1447,48 @@ fn parseArchive(self: *Elf, path: Path, must_link: bool) ParseError!void {
self.files.set(index, .{ .object = extracted });
const object = &self.files.items(.data)[index].object;
object.index = index;
object.alive = must_link;
object.alive = obj.must_link;
try object.parse(self);
try self.objects.append(gpa, index);
}
}
fn parseSharedObject(
fn openParseDsoReportingFailure(self: *Elf, path: Path) void {
const diags = &self.base.comp.link_diags;
const target = self.getTarget();
const dso = link.openDso(path, false, false, false) catch |err| {
switch (diags.failParse(path, "failed to open shared object {}: {s}", .{ path, @errorName(err) })) {
error.LinkFailure => return,
}
};
const gpa = self.base.comp.gpa;
parseDsoReportingFailure(gpa, diags, dso, &self.shared_objects, &self.files, target);
}
fn parseDsoReportingFailure(
gpa: Allocator,
diags: *Diags,
lib: SystemLib,
dso: link.Input.Dso,
shared_objects: *std.StringArrayHashMapUnmanaged(File.Index),
files: *std.MultiArrayList(File.Entry),
target: std.Target,
) void {
parseDso(gpa, diags, dso, shared_objects, files, target) catch |err| switch (err) {
error.LinkFailure => return, // already reported
error.BadMagic, error.UnexpectedEndOfFile => {
var notes = diags.addErrorWithNotes(2) catch return diags.setAllocFailure();
notes.addMsg("failed to parse shared object: {s}", .{@errorName(err)}) catch return diags.setAllocFailure();
notes.addNote("while parsing {}", .{dso.path}) catch return diags.setAllocFailure();
notes.addNote("{s}", .{@as([]const u8, "the file may be a GNU ld script, in which case it is not an ELF file but a text file referencing other libraries to link. In this case, avoid depending on the library, convince your system administrators to refrain from using this kind of file, or pass -fallow-so-scripts to force the compiler to check every shared library in case it is an ld script.")}) catch return diags.setAllocFailure();
},
else => |e| diags.addParseError(dso.path, "failed to parse shared object: {s}", .{@errorName(e)}),
};
}
fn parseDso(
gpa: Allocator,
diags: *Diags,
dso: link.Input.Dso,
shared_objects: *std.StringArrayHashMapUnmanaged(File.Index),
files: *std.MultiArrayList(File.Entry),
target: std.Target,
@ -1421,14 +1496,14 @@ fn parseSharedObject(
const tracy = trace(@src());
defer tracy.end();
const handle = try lib.path.root_dir.handle.openFile(lib.path.sub_path, .{});
const handle = dso.file;
defer handle.close();
const stat = Stat.fromFs(try handle.stat());
var header = try SharedObject.parseHeader(gpa, diags, lib.path, handle, stat, target);
var header = try SharedObject.parseHeader(gpa, diags, dso.path, handle, stat, target);
defer header.deinit(gpa);
const soname = header.soname() orelse lib.path.basename();
const soname = header.soname() orelse dso.path.basename();
const gop = try shared_objects.getOrPut(gpa, soname);
if (gop.found_existing) {
@ -1446,8 +1521,8 @@ fn parseSharedObject(
errdefer parsed.deinit(gpa);
const duped_path: Path = .{
.root_dir = lib.path.root_dir,
.sub_path = try gpa.dupe(u8, lib.path.sub_path),
.root_dir = dso.path.root_dir,
.sub_path = try gpa.dupe(u8, dso.path.sub_path),
};
errdefer gpa.free(duped_path.sub_path);
@ -1456,8 +1531,8 @@ fn parseSharedObject(
.parsed = parsed,
.path = duped_path,
.index = index,
.needed = lib.needed,
.alive = lib.needed,
.needed = dso.needed,
.alive = dso.needed,
.aliases = null,
.symbols = .empty,
.symbols_extra = .empty,
@ -1824,11 +1899,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
try man.addOptionalFile(self.version_script);
man.hash.add(self.allow_undefined_version);
man.hash.addOptional(self.enable_new_dtags);
for (comp.objects) |obj| {
_ = try man.addFilePath(obj.path, null);
man.hash.add(obj.must_link);
man.hash.add(obj.loption);
}
try link.hashInputs(&man, comp.link_inputs);
for (comp.c_object_table.keys()) |key| {
_ = try man.addFilePath(key.status.success.object_path, null);
}
@ -1875,7 +1946,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
}
man.hash.addOptionalBytes(self.soname);
man.hash.addOptional(comp.version);
try link.hashAddSystemLibs(&man, comp.system_libs);
man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
man.hash.add(self.base.allow_shlib_undefined);
man.hash.add(self.bind_global_refs_locally);
@ -1922,8 +1992,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
// 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 (comp.objects.len != 0)
break :blk comp.objects[0].path;
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;
@ -2178,21 +2247,26 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
// Positional arguments to the linker such as object files.
var whole_archive = false;
for (comp.objects) |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;
}
if (obj.loption) {
assert(obj.path.sub_path[0] == ':');
try argv.append("-l");
}
try argv.append(try obj.path.toString(arena));
}
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;
@ -2228,35 +2302,35 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
// Shared libraries.
if (is_exe_or_dyn_lib) {
const system_libs = comp.system_libs.keys();
const system_libs_values = comp.system_libs.values();
// Worst-case, we need an --as-needed argument for every lib, as well
// as one before and one after.
try argv.ensureUnusedCapacity(system_libs.len * 2 + 2);
argv.appendAssumeCapacity("--as-needed");
try argv.append("--as-needed");
var as_needed = true;
for (system_libs_values) |lib_info| {
const lib_as_needed = !lib_info.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;
},
}
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 lib_info.path.?.toString(arena));
}
// 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");

View file

@ -2,13 +2,13 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation, module_obj_path: ?Path
const gpa = comp.gpa;
const diags = &comp.link_diags;
for (comp.objects) |obj| {
switch (Compilation.classifyFileExt(obj.path.sub_path)) {
.object => parseObjectStaticLibReportingFailure(elf_file, obj.path),
.static_library => parseArchiveStaticLibReportingFailure(elf_file, obj.path),
else => diags.addParseError(obj.path, "unrecognized file extension", .{}),
}
}
for (comp.link_inputs) |link_input| switch (link_input) {
.object => |obj| parseObjectStaticLibReportingFailure(elf_file, obj.path),
.archive => |obj| parseArchiveStaticLibReportingFailure(elf_file, obj.path),
.dso_exact => unreachable,
.res => unreachable,
.dso => unreachable,
};
for (comp.c_object_table.keys()) |key| {
parseObjectStaticLibReportingFailure(elf_file, key.status.success.object_path);
@ -153,18 +153,18 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation, module_obj_path: ?Path
pub fn flushObject(elf_file: *Elf, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void {
const diags = &comp.link_diags;
for (comp.objects) |obj| {
elf_file.parseInputReportingFailure(obj.path, false, obj.must_link);
for (comp.link_inputs) |link_input| {
elf_file.parseInputReportingFailure(link_input);
}
// This is a set of object files emitted by clang in a single `build-exe` invocation.
// For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
// in this set.
for (comp.c_object_table.keys()) |key| {
elf_file.parseObjectReportingFailure(key.status.success.object_path);
elf_file.openParseObjectReportingFailure(key.status.success.object_path);
}
if (module_obj_path) |path| elf_file.parseObjectReportingFailure(path);
if (module_obj_path) |path| elf_file.openParseObjectReportingFailure(path);
if (diags.hasErrors()) return error.FlushFailure;

View file

@ -1,3 +1,7 @@
pub const Atom = @import("MachO/Atom.zig");
pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
pub const Relocation = @import("MachO/Relocation.zig");
base: link.File,
rpath_list: []const []const u8,
@ -114,8 +118,8 @@ headerpad_max_install_names: bool,
dead_strip_dylibs: bool,
/// Treatment of undefined symbols
undefined_treatment: UndefinedTreatment,
/// Resolved list of library search directories
lib_dirs: []const []const u8,
/// TODO: delete this, libraries need to be resolved by the frontend instead
lib_directories: []const Directory,
/// Resolved list of framework search directories
framework_dirs: []const []const u8,
/// List of input frameworks
@ -213,7 +217,8 @@ pub fn createEmpty(
.platform = Platform.fromTarget(target),
.sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,
.undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
.lib_dirs = options.lib_dirs,
// TODO delete this, directories must instead be resolved by the frontend
.lib_directories = options.lib_directories,
.framework_dirs = options.framework_dirs,
.force_load_objc = options.force_load_objc,
};
@ -371,48 +376,44 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path);
if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path);
var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
var positionals = std.ArrayList(link.Input).init(gpa);
defer positionals.deinit();
try positionals.ensureUnusedCapacity(comp.objects.len);
positionals.appendSliceAssumeCapacity(comp.objects);
try positionals.ensureUnusedCapacity(comp.link_inputs.len);
for (comp.link_inputs) |link_input| switch (link_input) {
.dso => continue, // handled below
.object, .archive => positionals.appendAssumeCapacity(link_input),
.dso_exact => @panic("TODO"),
.res => unreachable,
};
// This is a set of object files emitted by clang in a single `build-exe` invocation.
// For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
// in this set.
try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len);
for (comp.c_object_table.keys()) |key| {
positionals.appendAssumeCapacity(.{ .path = key.status.success.object_path });
positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path));
}
if (module_obj_path) |path| try positionals.append(.{ .path = path });
if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
if (comp.config.any_sanitize_thread) {
try positionals.append(.{ .path = comp.tsan_lib.?.full_object_path });
try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path));
}
if (comp.config.any_fuzz) {
try positionals.append(.{ .path = comp.fuzzer_lib.?.full_object_path });
try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path));
}
for (positionals.items) |obj| {
self.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err|
diags.addParseError(obj.path, "failed to read input file: {s}", .{@errorName(err)});
for (positionals.items) |link_input| {
self.classifyInputFile(link_input) catch |err|
diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
}
var system_libs = std.ArrayList(SystemLib).init(gpa);
defer system_libs.deinit();
// libs
try system_libs.ensureUnusedCapacity(comp.system_libs.values().len);
for (comp.system_libs.values()) |info| {
system_libs.appendAssumeCapacity(.{
.needed = info.needed,
.weak = info.weak,
.path = info.path.?,
});
}
// frameworks
try system_libs.ensureUnusedCapacity(self.frameworks.len);
for (self.frameworks) |info| {
@ -436,20 +437,24 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
else => |e| return e, // TODO: convert into an error
};
for (system_libs.items) |lib| {
self.classifyInputFile(lib.path, lib, false) catch |err|
diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
}
for (comp.link_inputs) |link_input| switch (link_input) {
.object, .archive, .dso_exact => continue,
.res => unreachable,
.dso => {
self.classifyInputFile(link_input) catch |err|
diags.addParseError(link_input.path().?, "failed to parse input file: {s}", .{@errorName(err)});
},
};
// Finally, link against compiler_rt.
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;
};
if (compiler_rt_path) |path| {
self.classifyInputFile(path, .{ .path = path }, false) catch |err|
diags.addParseError(path, "failed to parse input file: {s}", .{@errorName(err)});
if (comp.compiler_rt_lib) |crt_file| {
const path = crt_file.full_object_path;
self.classifyInputFile(try link.openArchiveInput(diags, path)) catch |err|
diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
} else if (comp.compiler_rt_obj) |crt_file| {
const path = crt_file.full_object_path;
self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err|
diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
}
try self.parseInputFiles();
@ -596,9 +601,12 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
}
if (self.base.isRelocatable()) {
for (comp.objects) |obj| {
try argv.append(try obj.path.toString(arena));
}
for (comp.link_inputs) |link_input| switch (link_input) {
.object, .archive => |obj| try argv.append(try obj.path.toString(arena)),
.res => |res| try argv.append(try res.path.toString(arena)),
.dso => |dso| try argv.append(try dso.path.toString(arena)),
.dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }),
};
for (comp.c_object_table.keys()) |key| {
try argv.append(try key.status.success.object_path.toString(arena));
@ -678,13 +686,15 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
try argv.append("dynamic_lookup");
}
for (comp.objects) |obj| {
// TODO: verify this
if (obj.must_link) {
try argv.append("-force_load");
}
try argv.append(try obj.path.toString(arena));
}
for (comp.link_inputs) |link_input| switch (link_input) {
.dso => continue, // handled below
.res => unreachable, // windows only
.object, .archive => |obj| {
if (obj.must_link) try argv.append("-force_load"); // TODO: verify this
try argv.append(try obj.path.toString(arena));
},
.dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }),
};
for (comp.c_object_table.keys()) |key| {
try argv.append(try key.status.success.object_path.toString(arena));
@ -703,21 +713,25 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena));
}
for (self.lib_dirs) |lib_dir| {
const arg = try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir});
for (self.lib_directories) |lib_directory| {
// TODO delete this, directories must instead be resolved by the frontend
const arg = try std.fmt.allocPrint(arena, "-L{s}", .{lib_directory.path orelse "."});
try argv.append(arg);
}
for (comp.system_libs.keys()) |l_name| {
const info = comp.system_libs.get(l_name).?;
const arg = if (info.needed)
try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
else if (info.weak)
try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name})
else
try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
try argv.append(arg);
}
for (comp.link_inputs) |link_input| switch (link_input) {
.object, .archive, .dso_exact => continue, // handled above
.res => unreachable, // windows only
.dso => |dso| {
if (dso.needed) {
try argv.appendSlice(&.{ "-needed-l", try dso.path.toString(arena) });
} else if (dso.weak) {
try argv.appendSlice(&.{ "-weak-l", try dso.path.toString(arena) });
} else {
try argv.appendSlice(&.{ "-l", try dso.path.toString(arena) });
}
},
};
for (self.framework_dirs) |f_dir| {
try argv.append("-F");
@ -751,6 +765,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
Compilation.dump_argv(argv.items);
}
/// TODO delete this, libsystem must be resolved when setting up the compilationt pipeline
pub fn resolveLibSystem(
self: *MachO,
arena: Allocator,
@ -774,8 +789,8 @@ pub fn resolveLibSystem(
},
};
for (self.lib_dirs) |dir| {
if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success;
for (self.lib_directories) |directory| {
if (try accessLibPath(arena, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success;
}
diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
@ -789,13 +804,14 @@ pub fn resolveLibSystem(
});
}
pub fn classifyInputFile(self: *MachO, path: Path, lib: SystemLib, must_link: bool) !void {
pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
const tracy = trace(@src());
defer tracy.end();
const path, const file = input.pathAndFile().?;
// TODO don't classify now, it's too late. The input file has already been classified
log.debug("classifying input file {}", .{path});
const file = try path.root_dir.handle.openFile(path.sub_path, .{});
const fh = try self.addFileHandle(file);
var buffer: [Archive.SARMAG]u8 = undefined;
@ -806,17 +822,17 @@ pub fn classifyInputFile(self: *MachO, path: Path, lib: SystemLib, must_link: bo
if (h.magic != macho.MH_MAGIC_64) break :blk;
switch (h.filetype) {
macho.MH_OBJECT => try self.addObject(path, fh, offset),
macho.MH_DYLIB => _ = try self.addDylib(lib, true, fh, offset),
macho.MH_DYLIB => _ = try self.addDylib(.fromLinkInput(input), true, fh, offset),
else => return error.UnknownFileType,
}
return;
}
if (readArMagic(file, offset, &buffer) catch null) |ar_magic| blk: {
if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk;
try self.addArchive(lib, must_link, fh, fat_arch);
try self.addArchive(input.archive, fh, fat_arch);
return;
}
_ = try self.addTbd(lib, true, fh);
_ = try self.addTbd(.fromLinkInput(input), true, fh);
}
fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch {
@ -903,7 +919,7 @@ fn parseInputFileWorker(self: *MachO, file: File) void {
};
}
fn addArchive(self: *MachO, lib: SystemLib, must_link: bool, handle: File.HandleIndex, fat_arch: ?fat.Arch) !void {
fn addArchive(self: *MachO, lib: link.Input.Object, handle: File.HandleIndex, fat_arch: ?fat.Arch) !void {
const tracy = trace(@src());
defer tracy.end();
@ -918,7 +934,7 @@ fn addArchive(self: *MachO, lib: SystemLib, must_link: bool, handle: File.Handle
self.files.set(index, .{ .object = unpacked });
const object = &self.files.items(.data)[index].object;
object.index = index;
object.alive = must_link or lib.needed; // TODO: or self.options.all_load;
object.alive = lib.must_link; // TODO: or self.options.all_load;
object.hidden = lib.hidden;
try self.objects.append(gpa, index);
}
@ -993,6 +1009,7 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool {
return false;
}
/// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline
fn accessLibPath(
arena: Allocator,
test_path: *std.ArrayList(u8),
@ -1051,9 +1068,11 @@ fn parseDependentDylibs(self: *MachO) !void {
if (self.dylibs.items.len == 0) return;
const gpa = self.base.comp.gpa;
const lib_dirs = self.lib_dirs;
const framework_dirs = self.framework_dirs;
// TODO delete this, directories must instead be resolved by the frontend
const lib_directories = self.lib_directories;
var arena_alloc = std.heap.ArenaAllocator.init(gpa);
defer arena_alloc.deinit();
const arena = arena_alloc.allocator();
@ -1094,9 +1113,9 @@ fn parseDependentDylibs(self: *MachO) !void {
// Library
const lib_name = eatPrefix(stem, "lib") orelse stem;
for (lib_dirs) |dir| {
for (lib_directories) |lib_directory| {
test_path.clearRetainingCapacity();
if (try accessLibPath(arena, &test_path, &checked_paths, dir, lib_name)) break :full_path test_path.items;
if (try accessLibPath(arena, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items;
}
}
@ -4366,6 +4385,24 @@ const SystemLib = struct {
hidden: bool = false,
reexport: bool = false,
must_link: bool = false,
fn fromLinkInput(link_input: link.Input) SystemLib {
return switch (link_input) {
.dso_exact => unreachable,
.res => unreachable,
.object, .archive => |obj| .{
.path = obj.path,
.must_link = obj.must_link,
.hidden = obj.hidden,
},
.dso => |dso| .{
.path = dso.path,
.needed = dso.needed,
.weak = dso.weak,
.reexport = dso.reexport,
},
};
}
};
pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout;
@ -5303,17 +5340,16 @@ const Air = @import("../Air.zig");
const Alignment = Atom.Alignment;
const Allocator = mem.Allocator;
const Archive = @import("MachO/Archive.zig");
pub const Atom = @import("MachO/Atom.zig");
const AtomicBool = std.atomic.Value(bool);
const Bind = bind.Bind;
const Cache = std.Build.Cache;
const Path = Cache.Path;
const CodeSignature = @import("MachO/CodeSignature.zig");
const Compilation = @import("../Compilation.zig");
const DataInCode = synthetic.DataInCode;
pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
const Directory = Cache.Directory;
const Dylib = @import("MachO/Dylib.zig");
const ExportTrie = @import("MachO/dyld_info/Trie.zig");
const Path = Cache.Path;
const File = @import("MachO/file.zig").File;
const GotSection = synthetic.GotSection;
const Hash = std.hash.Wyhash;
@ -5329,7 +5365,6 @@ const Md5 = std.crypto.hash.Md5;
const Zcu = @import("../Zcu.zig");
const InternPool = @import("../InternPool.zig");
const Rebase = @import("MachO/dyld_info/Rebase.zig");
pub const Relocation = @import("MachO/Relocation.zig");
const StringTable = @import("StringTable.zig");
const StubsSection = synthetic.StubsSection;
const StubsHelperSection = synthetic.StubsHelperSection;

View file

@ -3,16 +3,16 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
const diags = &macho_file.base.comp.link_diags;
// TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
var positionals = std.ArrayList(link.Input).init(gpa);
defer positionals.deinit();
try positionals.ensureUnusedCapacity(comp.objects.len);
positionals.appendSliceAssumeCapacity(comp.objects);
try positionals.ensureUnusedCapacity(comp.link_inputs.len);
positionals.appendSliceAssumeCapacity(comp.link_inputs);
for (comp.c_object_table.keys()) |key| {
try positionals.append(.{ .path = key.status.success.object_path });
try positionals.append(try link.openObjectInput(diags, key.status.success.object_path));
}
if (module_obj_path) |path| try positionals.append(.{ .path = path });
if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
if (macho_file.getZigObject() == null and positionals.items.len == 1) {
// Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
@ -20,7 +20,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
// the *only* input file over.
// TODO: in the future, when we implement `dsymutil` alternative directly in the Zig
// compiler, investigate if we can get rid of this `if` prong here.
const path = positionals.items[0].path;
const path = positionals.items[0].path().?;
const in_file = try path.root_dir.handle.openFile(path.sub_path, .{});
const stat = try in_file.stat();
const amt = try in_file.copyRangeAll(0, macho_file.base.file.?, 0, stat.size);
@ -28,9 +28,9 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
return;
}
for (positionals.items) |obj| {
macho_file.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err|
diags.addParseError(obj.path, "failed to read input file: {s}", .{@errorName(err)});
for (positionals.items) |link_input| {
macho_file.classifyInputFile(link_input) catch |err|
diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
}
if (diags.hasErrors()) return error.FlushFailure;
@ -72,25 +72,25 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
const gpa = comp.gpa;
const diags = &macho_file.base.comp.link_diags;
var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
var positionals = std.ArrayList(link.Input).init(gpa);
defer positionals.deinit();
try positionals.ensureUnusedCapacity(comp.objects.len);
positionals.appendSliceAssumeCapacity(comp.objects);
try positionals.ensureUnusedCapacity(comp.link_inputs.len);
positionals.appendSliceAssumeCapacity(comp.link_inputs);
for (comp.c_object_table.keys()) |key| {
try positionals.append(.{ .path = key.status.success.object_path });
try positionals.append(try link.openObjectInput(diags, key.status.success.object_path));
}
if (module_obj_path) |path| try positionals.append(.{ .path = path });
if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
if (comp.include_compiler_rt) {
try positionals.append(.{ .path = comp.compiler_rt_obj.?.full_object_path });
try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path));
}
for (positionals.items) |obj| {
macho_file.classifyInputFile(obj.path, .{ .path = obj.path }, obj.must_link) catch |err|
diags.addParseError(obj.path, "failed to read input file: {s}", .{@errorName(err)});
for (positionals.items) |link_input| {
macho_file.classifyInputFile(link_input) catch |err|
diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
}
if (diags.hasErrors()) return error.FlushFailure;
@ -745,20 +745,15 @@ fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
try macho_file.base.file.?.pwriteAll(mem.asBytes(&header), 0);
}
const std = @import("std");
const Path = std.Build.Cache.Path;
const WaitGroup = std.Thread.WaitGroup;
const assert = std.debug.assert;
const build_options = @import("build_options");
const eh_frame = @import("eh_frame.zig");
const fat = @import("fat.zig");
const link = @import("../../link.zig");
const load_commands = @import("load_commands.zig");
const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const state_log = std.log.scoped(.link_state);
const std = @import("std");
const trace = @import("../../tracy.zig").trace;
const Path = std.Build.Cache.Path;
const Archive = @import("Archive.zig");
const Atom = @import("Atom.zig");
@ -767,3 +762,9 @@ const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
const Symbol = @import("Symbol.zig");
const build_options = @import("build_options");
const eh_frame = @import("eh_frame.zig");
const fat = @import("fat.zig");
const link = @import("../../link.zig");
const load_commands = @import("load_commands.zig");
const trace = @import("../../tracy.zig").trace;

View file

@ -637,14 +637,6 @@ fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) !
return loc;
}
fn parseInputFiles(wasm: *Wasm, files: []const []const u8) !void {
for (files) |path| {
if (try wasm.parseObjectFile(path)) continue;
if (try wasm.parseArchive(path, false)) continue; // load archives lazily
log.warn("Unexpected file format at path: '{s}'", .{path});
}
}
/// Parses the object file from given path. Returns true when the given file was an object
/// file and parsed successfully. Returns false when file is not an object file.
/// May return an error instead when parsing failed.
@ -2522,7 +2514,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
// Positional arguments to the linker such as object files and static archives.
// TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
var positionals = std.ArrayList([]const u8).init(arena);
try positionals.ensureUnusedCapacity(comp.objects.len);
try positionals.ensureUnusedCapacity(comp.link_inputs.len);
const target = comp.root_mod.resolved_target.result;
const output_mode = comp.config.output_mode;
@ -2566,9 +2558,12 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
try positionals.append(path);
}
for (comp.objects) |object| {
try positionals.append(try object.path.toString(arena));
}
for (comp.link_inputs) |link_input| switch (link_input) {
.object, .archive => |obj| try positionals.append(try obj.path.toString(arena)),
.dso => |dso| try positionals.append(try dso.path.toString(arena)),
.dso_exact => unreachable, // forbidden by frontend
.res => unreachable, // windows only
};
for (comp.c_object_table.keys()) |c_object| {
try positionals.append(try c_object.status.success.object_path.toString(arena));
@ -2577,7 +2572,11 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
if (comp.compiler_rt_lib) |lib| try positionals.append(try lib.full_object_path.toString(arena));
if (comp.compiler_rt_obj) |obj| try positionals.append(try obj.full_object_path.toString(arena));
try wasm.parseInputFiles(positionals.items);
for (positionals.items) |path| {
if (try wasm.parseObjectFile(path)) continue;
if (try wasm.parseArchive(path, false)) continue; // load archives lazily
log.warn("Unexpected file format at path: '{s}'", .{path});
}
if (wasm.zig_object_index != .null) {
try wasm.resolveSymbolsInObject(wasm.zig_object_index);
@ -3401,10 +3400,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
comptime assert(Compilation.link_hash_implementation_version == 14);
for (comp.objects) |obj| {
_ = try man.addFilePath(obj.path, null);
man.hash.add(obj.must_link);
}
try link.hashInputs(&man, comp.link_inputs);
for (comp.c_object_table.keys()) |key| {
_ = try man.addFilePath(key.status.success.object_path, null);
}
@ -3458,8 +3454,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
// 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 (comp.objects.len != 0)
break :blk comp.objects[0].path;
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;
@ -3621,16 +3616,23 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
// Positional arguments to the linker such as object files.
var whole_archive = false;
for (comp.objects) |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));
}
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;

File diff suppressed because it is too large Load diff