stage2: progress towards LLD linking

* add `zig libc` command
 * add `--libc` CLI and integrate it with Module and linker code
 * implement libc detection and paths resolution
 * port LLD ELF linker line construction to stage2
 * integrate dynamic linker option into Module and linker code
 * implement default link_mode detection and error handling if
   user requests static when it cannot be fulfilled
 * integrate more linker options
 * implement detection of .so.X.Y.Z file extension as a shared object
   file. nice try, you can't fool me.
 * correct usage text for -dynamic and -static
This commit is contained in:
Andrew Kelley
2020-09-09 22:24:17 -07:00
parent 5746a8658e
commit e05ecbf165
7 changed files with 634 additions and 64 deletions

View File

@@ -24,6 +24,7 @@ const liveness = @import("liveness.zig");
const astgen = @import("astgen.zig");
const zir_sema = @import("zir_sema.zig");
const build_options = @import("build_options");
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
/// General-purpose allocator. Used for both temporary and long-term storage.
gpa: *Allocator,
@@ -82,8 +83,6 @@ next_anon_name_index: usize = 0,
/// contains Decls that need to be deleted if they end up having no references to them.
deletion_set: std.ArrayListUnmanaged(*Decl) = .{},
/// Owned by Module.
root_name: []u8,
keep_source_files_loaded: bool,
use_clang: bool,
sanitize_c: bool,
@@ -106,6 +105,19 @@ zig_cache_dir_path: []const u8,
libc_include_dir_list: []const []const u8,
rand: *std.rand.Random,
/// Populated when we build libc++.a. A WorkItem to build this is placed in the queue
/// and resolved before calling linker.flush().
libcxx_static_lib: ?[]const u8 = null,
/// Populated when we build libc++abi.a. A WorkItem to build this is placed in the queue
/// and resolved before calling linker.flush().
libcxxabi_static_lib: ?[]const u8 = null,
/// Populated when we build libunwind.a. A WorkItem to build this is placed in the queue
/// and resolved before calling linker.flush().
libunwind_static_lib: ?[]const u8 = null,
/// Populated when we build c.a. A WorkItem to build this is placed in the queue
/// and resolved before calling linker.flush().
libc_static_lib: ?[]const u8 = null,
pub const InnerError = error{ OutOfMemory, AnalysisFail };
const WorkItem = union(enum) {
@@ -932,6 +944,7 @@ pub const InitOptions = struct {
root_pkg: ?*Package,
output_mode: std.builtin.OutputMode,
rand: *std.rand.Random,
dynamic_linker: ?[]const u8 = null,
bin_file_dir_path: ?[]const u8 = null,
bin_file_dir: ?std.fs.Dir = null,
bin_file_path: []const u8,
@@ -941,6 +954,7 @@ pub const InitOptions = struct {
optimize_mode: std.builtin.Mode = .Debug,
keep_source_files_loaded: bool = false,
clang_argv: []const []const u8 = &[0][]const u8{},
lld_argv: []const []const u8 = &[0][]const u8{},
lib_dirs: []const []const u8 = &[0][]const u8{},
rpath_list: []const []const u8 = &[0][]const u8{},
c_source_files: []const []const u8 = &[0][]const u8{},
@@ -957,10 +971,11 @@ pub const InitOptions = struct {
use_clang: ?bool = null,
rdynamic: bool = false,
strip: bool = false,
is_native_os: bool,
link_eh_frame_hdr: bool = false,
linker_script: ?[]const u8 = null,
version_script: ?[]const u8 = null,
override_soname: ?[]const u8 = null,
linker_optimization: ?[]const u8 = null,
linker_gc_sections: ?bool = null,
function_sections: ?bool = null,
linker_allow_shlib_undefined: ?bool = null,
@@ -969,8 +984,10 @@ pub const InitOptions = struct {
linker_z_nodelete: bool = false,
linker_z_defs: bool = false,
clang_passthrough_mode: bool = false,
stack_size_override: u64 = 0,
stack_size_override: ?u64 = null,
self_exe_path: ?[]const u8 = null,
version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 },
libc_installation: ?*const LibCInstallation = null,
};
pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
@@ -1002,6 +1019,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
options.frameworks.len != 0 or
options.system_libs.len != 0 or
options.link_libc or options.link_libcpp or
options.link_eh_frame_hdr or
options.linker_script != null or options.version_script != null)
{
break :blk true;
@@ -1017,6 +1035,35 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
break :blk false;
};
const must_dynamic_link = dl: {
if (target_util.cannotDynamicLink(options.target))
break :dl false;
if (target_util.osRequiresLibC(options.target))
break :dl true;
if (options.link_libc and options.target.isGnuLibC())
break :dl true;
if (options.system_libs.len != 0)
break :dl true;
break :dl false;
};
const default_link_mode: std.builtin.LinkMode = if (must_dynamic_link) .Dynamic else .Static;
const link_mode: std.builtin.LinkMode = if (options.link_mode) |lm| blk: {
if (lm == .Static and must_dynamic_link) {
return error.UnableToStaticLink;
}
break :blk lm;
} else default_link_mode;
const libc_dirs = try detectLibCIncludeDirs(
arena,
options.zig_lib_dir,
options.target,
options.is_native_os,
options.link_libc,
options.libc_installation,
);
const bin_file = try link.File.openPath(gpa, .{
.dir = options.bin_file_dir orelse std.fs.cwd(),
.dir_path = options.bin_file_dir_path,
@@ -1024,8 +1071,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
.root_name = root_name,
.root_pkg = options.root_pkg,
.target = options.target,
.dynamic_linker = options.dynamic_linker,
.output_mode = options.output_mode,
.link_mode = options.link_mode orelse .Static,
.link_mode = link_mode,
.object_format = ofmt,
.optimize_mode = options.optimize_mode,
.use_lld = use_lld,
@@ -1039,7 +1087,22 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
.lib_dirs = options.lib_dirs,
.rpath_list = options.rpath_list,
.strip = options.strip,
.is_native_os = options.is_native_os,
.function_sections = options.function_sections orelse false,
.allow_shlib_undefined = options.linker_allow_shlib_undefined,
.bind_global_refs_locally = options.linker_bind_global_refs_locally orelse false,
.z_nodelete = options.linker_z_nodelete,
.z_defs = options.linker_z_defs,
.stack_size_override = options.stack_size_override,
.linker_script = options.linker_script,
.version_script = options.version_script,
.gc_sections = options.linker_gc_sections,
.eh_frame_hdr = options.link_eh_frame_hdr,
.rdynamic = options.rdynamic,
.extra_lld_args = options.lld_argv,
.override_soname = options.override_soname,
.version = options.version,
.libc_installation = libc_dirs.libc_installation,
});
errdefer bin_file.destroy();
@@ -1146,13 +1209,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
break :blk true;
};
const libc_include_dir_list = try detectLibCIncludeDirs(
arena,
options.zig_lib_dir,
options.target,
options.link_libc,
);
const sanitize_c: bool = options.want_sanitize_c orelse switch (options.optimize_mode) {
.Debug, .ReleaseSafe => true,
.ReleaseSmall, .ReleaseFast => false,
@@ -1163,7 +1219,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
.arena_state = arena_allocator.state,
.zig_lib_dir = options.zig_lib_dir,
.zig_cache_dir_path = zig_cache_dir_path,
.root_name = root_name,
.root_pkg = options.root_pkg,
.root_scope = root_scope,
.bin_file = bin_file,
@@ -1174,7 +1229,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
.c_source_files = options.c_source_files,
.cache = cache,
.self_exe_path = options.self_exe_path,
.libc_include_dir_list = libc_include_dir_list,
.libc_include_dir_list = libc_dirs.libc_include_dir_list,
.sanitize_c = sanitize_c,
.rand = options.rand,
.clang_passthrough_mode = options.clang_passthrough_mode,
@@ -1544,7 +1599,10 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void {
// directly to the output file.
const direct_o = mod.c_source_files.len == 1 and mod.root_pkg == null and
mod.bin_file.options.output_mode == .Obj and mod.bin_file.options.objects.len == 0;
const o_basename_noext = if (direct_o) mod.root_name else mem.split(c_source_basename, ".").next().?;
const o_basename_noext = if (direct_o)
mod.bin_file.options.root_name
else
mem.split(c_source_basename, ".").next().?;
const o_basename = try std.fmt.allocPrint(arena, "{}{}", .{ o_basename_noext, mod.getTarget().oFileExt() });
// We can't know the digest until we do the C compiler invocation, so we need a temporary filename.
@@ -1749,7 +1807,7 @@ fn addCCArgs(
try argv.append(p);
}
},
.assembly, .ll, .bc, .unknown => {},
.so, .assembly, .ll, .bc, .unknown => {},
}
// TODO CLI args for cpu features when compiling assembly
//for (size_t i = 0; i < g->zig_target->llvm_cpu_features_asm_len; i += 1) {
@@ -4259,6 +4317,7 @@ pub const FileExt = enum {
ll,
bc,
assembly,
so,
unknown,
};
@@ -4290,10 +4349,36 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
return .assembly;
} else if (mem.endsWith(u8, filename, ".h")) {
return .h;
} else {
// TODO look for .so, .so.X, .so.X.Y, .so.X.Y.Z
return .unknown;
} else if (mem.endsWith(u8, filename, ".so")) {
return .so;
}
// Look for .so.X, .so.X.Y, .so.X.Y.Z
var it = mem.split(filename, ".");
_ = it.next().?;
var so_txt = it.next() orelse return .unknown;
while (!mem.eql(u8, so_txt, "so")) {
so_txt = it.next() orelse return .unknown;
}
const n1 = it.next() orelse return .unknown;
const n2 = it.next();
const n3 = it.next();
_ = std.fmt.parseInt(u32, n1, 10) catch return .unknown;
if (n2) |x| _ = std.fmt.parseInt(u32, x, 10) catch return .unknown;
if (n3) |x| _ = std.fmt.parseInt(u32, x, 10) catch return .unknown;
if (it.next() != null) return .unknown;
return .so;
}
test "classifyFileExt" {
std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc"));
std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim"));
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so"));
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1"));
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1.2"));
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1.2.3"));
std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.so.1.2.3~"));
}
fn haveFramePointer(mod: *Module) bool {
@@ -4303,16 +4388,29 @@ fn haveFramePointer(mod: *Module) bool {
};
}
const LibCDirs = struct {
libc_include_dir_list: []const []const u8,
libc_installation: ?*const LibCInstallation,
};
fn detectLibCIncludeDirs(
arena: *Allocator,
zig_lib_dir: []const u8,
target: Target,
is_native_os: bool,
link_libc: bool,
) ![]const []const u8 {
if (!link_libc) return &[0][]u8{};
libc_installation: ?*const LibCInstallation,
) !LibCDirs {
if (!link_libc) {
return LibCDirs{
.libc_include_dir_list = &[0][]u8{},
.libc_installation = null,
};
}
// TODO Support --libc file explicitly providing libc paths. Or not? Maybe we are better off
// deleting that feature.
if (libc_installation) |lci| {
return detectLibCFromLibCInstallation(arena, target, lci);
}
if (target_util.canBuildLibC(target)) {
const generic_name = target_util.libCGenericName(target);
@@ -4348,9 +4446,52 @@ fn detectLibCIncludeDirs(
list[1] = generic_include_dir;
list[2] = arch_os_include_dir;
list[3] = generic_os_include_dir;
return list;
return LibCDirs{
.libc_include_dir_list = list,
.libc_installation = null,
};
}
// TODO finish porting detect_libc from codegen.cpp
return error.LibCDetectionUnimplemented;
if (is_native_os) {
const libc = try arena.create(LibCInstallation);
libc.* = try LibCInstallation.findNative(.{ .allocator = arena });
return detectLibCFromLibCInstallation(arena, target, libc);
}
return LibCDirs{
.libc_include_dir_list = &[0][]u8{},
.libc_installation = null,
};
}
fn detectLibCFromLibCInstallation(arena: *Allocator, target: Target, lci: *const LibCInstallation) !LibCDirs {
var list = std.ArrayList([]const u8).init(arena);
try list.ensureCapacity(4);
list.appendAssumeCapacity(lci.include_dir.?);
const is_redundant = mem.eql(u8, lci.sys_include_dir.?, lci.include_dir.?);
if (!is_redundant) list.appendAssumeCapacity(lci.sys_include_dir.?);
if (target.os.tag == .windows) {
if (std.fs.path.dirname(lci.include_dir.?)) |include_dir_parent| {
const um_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "um" });
list.appendAssumeCapacity(um_dir);
const shared_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "shared" });
list.appendAssumeCapacity(shared_dir);
}
}
return LibCDirs{
.libc_include_dir_list = list.items,
.libc_installation = lci,
};
}
pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 {
// TODO port support for building crt files from stage1
const lci = mod.bin_file.options.libc_installation orelse return error.LibCInstallationNotAvailable;
const crt_dir_path = lci.crt_dir orelse return error.LibCInstallationMissingCRTDir;
const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename });
return full_path;
}