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

View File

@ -11,6 +11,8 @@ const is_gnu = Target.current.isGnu();
usingnamespace @import("windows_sdk.zig");
// TODO Rework this abstraction to use std.log instead of taking a stderr stream.
/// See the render function implementation for documentation of the fields.
pub const LibCInstallation = struct {
include_dir: ?[]const u8 = null,

View File

@ -6,6 +6,7 @@ const trace = @import("tracy.zig").trace;
const Package = @import("Package.zig");
const Type = @import("type.zig").Type;
const build_options = @import("build_options");
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
@ -23,6 +24,7 @@ pub const Options = struct {
optimize_mode: std.builtin.Mode,
root_name: []const u8,
root_pkg: ?*const Package,
dynamic_linker: ?[]const u8 = null,
/// Used for calculating how much space to reserve for symbols in case the binary file
/// does not already have a symbol table.
symbol_count_hint: u64 = 32,
@ -30,6 +32,7 @@ pub const Options = struct {
/// the binary file does not already have such a section.
program_code_size_hint: u64 = 256 * 1024,
entry_addr: ?u64 = null,
stack_size_override: ?u64 = null,
/// Set to `true` to omit debug info.
strip: bool = false,
/// If this is true then this link code is responsible for outputting an object
@ -44,6 +47,19 @@ pub const Options = struct {
link_libc: bool = false,
link_libcpp: bool = false,
function_sections: bool = false,
eh_frame_hdr: bool = false,
rdynamic: bool = false,
z_nodelete: bool = false,
z_defs: bool = false,
bind_global_refs_locally: bool,
is_native_os: bool,
gc_sections: ?bool = null,
allow_shlib_undefined: ?bool = null,
linker_script: ?[]const u8 = null,
version_script: ?[]const u8 = null,
override_soname: ?[]const u8 = null,
/// Extra args passed directly to LLD. Ignored when not linking with LLD.
extra_lld_args: []const []const u8 = &[0][]const u8,
objects: []const []const u8 = &[0][]const u8{},
framework_dirs: []const []const u8 = &[0][]const u8{},
@ -52,6 +68,9 @@ pub const Options = struct {
lib_dirs: []const []const u8 = &[0][]const u8{},
rpath_list: []const []const u8 = &[0][]const u8{},
version: std.builtin.Version,
libc_installation: ?*const LibCInstallation,
pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
return if (options.use_lld) .Obj else options.output_mode;
}

View File

@ -18,6 +18,7 @@ const link = @import("../link.zig");
const File = link.File;
const Elf = @This();
const build_options = @import("build_options");
const target_util = @import("../target.zig");
const default_entry_addr = 0x8000000;
@ -709,12 +710,7 @@ pub const abbrev_parameter = 6;
pub fn flush(self: *Elf, module: *Module) !void {
if (build_options.have_llvm and self.base.options.use_lld) {
// If there is no Zig code to compile, then we should skip flushing the output file because it
// will not be part of the linker line anyway.
if (module.root_pkg != null) {
try self.flushInner(module);
}
std.debug.print("TODO create an LLD command line and invoke it\n", .{});
return self.linkWithLLD(module);
} else {
switch (self.base.options.effectiveOutputMode()) {
.Exe, .Obj => {},
@ -1202,6 +1198,275 @@ fn flushInner(self: *Elf, module: *Module) !void {
assert(!self.debug_strtab_dirty);
}
fn linkWithLLD(self: *Elf, module: *Module) !void {
// If there is no Zig code to compile, then we should skip flushing the output file because it
// will not be part of the linker line anyway.
if (module.root_pkg != null) {
try self.flushInner(module);
}
var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
defer arena_allocator.deinit();
const arena = &arena_allocator.allocator;
const target = self.base.options.target;
const is_obj = self.base.options.output_mode == .Obj;
// Create an LLD command line and invoke it.
var argv = std.ArrayList([]const u8).init(self.base.allocator);
defer argv.deinit();
// Even though we're calling LLD as a library it thinks the first argument is its own exe name.
try argv.append("lld");
if (is_obj) {
try argv.append("-r");
}
if (self.base.options.output_mode == .Lib and
self.base.options.link_mode == .Static and
!target.isWasm())
{
// TODO port the code from link.cpp
return error.TODOMakeArchive;
}
const link_in_crt = self.base.options.link_libc and self.base.options.output_mode == .Exe;
try argv.append("-error-limit=0");
if (self.base.options.output_mode == .Exe) {
try argv.append("-z");
const stack_size = self.base.options.stack_size_override orelse 16777216;
const arg = try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size});
try argv.append(arg);
}
if (self.base.options.linker_script) |linker_script| {
try argv.append("-T");
try argv.append(linker_script);
}
const gc_sections = self.base.options.gc_sections orelse !is_obj;
if (gc_sections) {
try argv.append("--gc-sections");
}
if (self.base.options.eh_frame_hdr) {
try argv.append("--eh-frame-hdr");
}
if (self.base.options.rdynamic) {
try argv.append("--export-dynamic");
}
try argv.appendSlice(self.base.options.extra_lld_args);
if (self.base.options.z_nodelete) {
try argv.append("-z");
try argv.append("nodelete");
}
if (self.base.options.z_defs) {
try argv.append("-z");
try argv.append("defs");
}
if (getLDMOption(target)) |ldm| {
// Any target ELF will use the freebsd osabi if suffixed with "_fbsd".
const arg = if (target.os.tag == .freebsd)
try std.fmt.allocPrint(arena, "{}_fbsd", .{ldm})
else
ldm;
try argv.append("-m");
try argv.append(arg);
}
const is_lib = self.base.options.output_mode == .Lib;
const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
if (self.base.options.link_mode == .Static) {
if (target.cpu.arch.isARM() or target.cpu.arch.isThumb()) {
try argv.append("-Bstatic");
} else {
try argv.append("-static");
}
} else if (is_dyn_lib) {
try argv.append("-shared");
}
if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) {
try argv.append("-pie");
}
const full_out_path = if (self.base.options.dir_path) |dir_path|
try std.fs.path.join(arena, &[_][]const u8{dir_path, self.base.options.sub_path})
else
self.base.options.sub_path;
try argv.append("-o");
try argv.append(full_out_path);
if (link_in_crt) {
const crt1o: []const u8 = o: {
if (target.os.tag == .netbsd) {
break :o "crt0.o";
} else if (target.isAndroid()) {
if (self.base.options.link_mode == .Dynamic) {
break :o "crtbegin_dynamic.o";
} else {
break :o "crtbegin_static.o";
}
} else if (self.base.options.link_mode == .Static) {
break :o "crt1.o";
} else {
break :o "Scrt1.o";
}
};
try argv.append(try module.get_libc_crt_file(arena, crt1o));
if (target_util.libc_needs_crti_crtn(target)) {
try argv.append(try module.get_libc_crt_file(arena, "crti.o"));
}
}
// TODO rpaths
//for (size_t i = 0; i < g->rpath_list.length; i += 1) {
// Buf *rpath = g->rpath_list.at(i);
// add_rpath(lj, rpath);
//}
//if (g->each_lib_rpath) {
// for (size_t i = 0; i < g->lib_dirs.length; i += 1) {
// const char *lib_dir = g->lib_dirs.at(i);
// for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
// LinkLib *link_lib = g->link_libs_list.at(i);
// if (buf_eql_str(link_lib->name, "c")) {
// continue;
// }
// bool does_exist;
// Buf *test_path = buf_sprintf("%s/lib%s.so", lib_dir, buf_ptr(link_lib->name));
// if (os_file_exists(test_path, &does_exist) != ErrorNone) {
// zig_panic("link: unable to check if file exists: %s", buf_ptr(test_path));
// }
// if (does_exist) {
// add_rpath(lj, buf_create_from_str(lib_dir));
// break;
// }
// }
// }
//}
for (self.base.options.lib_dirs) |lib_dir| {
try argv.append("-L");
try argv.append(lib_dir);
}
if (self.base.options.link_libc) {
if (self.base.options.libc_installation) |libc_installation| {
try argv.append("-L");
try argv.append(libc_installation.crt_dir.?);
}
if (self.base.options.link_mode == .Dynamic and (is_dyn_lib or self.base.options.output_mode == .Exe)) {
if (self.base.options.dynamic_linker) |dynamic_linker| {
try argv.append("-dynamic-linker");
try argv.append(dynamic_linker);
}
}
}
if (is_dyn_lib) {
const soname = self.base.options.override_soname orelse
try std.fmt.allocPrint(arena, "lib{}.so.{}", .{self.base.options.root_name,
self.base.options.version.major,});
try argv.append("-soname");
try argv.append(soname);
if (self.base.options.version_script) |version_script| {
try argv.append("-version-script");
try argv.append(version_script);
}
}
// Positional arguments to the linker such as object files.
try argv.appendSlice(self.base.options.objects);
// TODO compiler-rt and libc
//if (!g->is_dummy_so && (g->out_type == OutTypeExe || is_dyn_lib)) {
// if (g->libc_link_lib == nullptr) {
// Buf *libc_a_path = build_c(g, OutTypeLib, lj->build_dep_prog_node);
// try argv.append(buf_ptr(libc_a_path));
// }
// Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib, lj->build_dep_prog_node);
// try argv.append(buf_ptr(compiler_rt_o_path));
//}
// Shared libraries.
try argv.ensureCapacity(argv.items.len + self.base.options.system_libs.len);
for (self.base.options.system_libs) |link_lib| {
// 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".
const ext = Module.classifyFileExt(link_lib);
const arg = if (ext == .so) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib});
argv.appendAssumeCapacity(arg);
}
if (!is_obj) {
// libc++ dep
if (self.base.options.link_libcpp) {
try argv.append(module.libcxxabi_static_lib.?);
try argv.append(module.libcxx_static_lib.?);
}
// libc dep
if (self.base.options.link_libc) {
if (self.base.options.libc_installation != null) {
if (self.base.options.link_mode == .Static) {
try argv.append("--start-group");
try argv.append("-lc");
try argv.append("-lm");
try argv.append("--end-group");
} else {
try argv.append("-lc");
try argv.append("-lm");
}
if (target.os.tag == .freebsd or target.os.tag == .netbsd) {
try argv.append("-lpthread");
}
} else if (target.isGnuLibC()) {
try argv.append(module.libunwind_static_lib.?);
// TODO here we need to iterate over the glibc libs and add the .so files to the linker line.
std.log.warn("TODO port add_glibc_libs to stage2", .{});
try argv.append(try module.get_libc_crt_file(arena, "libc_nonshared.a"));
} else if (target.isMusl()) {
try argv.append(module.libunwind_static_lib.?);
try argv.append(module.libc_static_lib.?);
} else if (self.base.options.link_libcpp) {
try argv.append(module.libunwind_static_lib.?);
} else {
unreachable; // Compiler was supposed to emit an error for not being able to provide libc.
}
}
}
// crt end
if (link_in_crt) {
if (target.isAndroid()) {
try argv.append(try module.get_libc_crt_file(arena, "crtend_android.o"));
} else if (target_util.libc_needs_crti_crtn(target)) {
try argv.append(try module.get_libc_crt_file(arena, "crtn.o"));
}
}
const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
if (allow_shlib_undefined) {
try argv.append("--allow-shlib-undefined");
}
if (self.base.options.bind_global_refs_locally) {
try argv.append("-Bsymbolic");
}
for (argv.items) |arg| {
std.debug.print("{} ", .{arg});
}
@panic("invoke LLD");
}
fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void {
const target_endian = self.base.options.target.cpu.arch.endian();
switch (self.ptr_width) {
@ -2616,3 +2881,36 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr {
.sh_entsize = @intCast(u32, shdr.sh_entsize),
};
}
fn getLDMOption(target: std.Target) ?[]const u8 {
switch (target.cpu.arch) {
.i386 => return "elf_i386",
.aarch64 => return "aarch64linux",
.aarch64_be => return "aarch64_be_linux",
.arm, .thumb => return "armelf_linux_eabi",
.armeb, .thumbeb => return "armebelf_linux_eabi",
.powerpc => return "elf32ppclinux",
.powerpc64 => return "elf64ppc",
.powerpc64le => return "elf64lppc",
.sparc, .sparcel => return "elf32_sparc",
.sparcv9 => return "elf64_sparc",
.mips => return "elf32btsmip",
.mipsel => return "elf32ltsmip",
.mips64 => return "elf64btsmip",
.mips64el => return "elf64ltsmip",
.s390x => return "elf64_s390",
.x86_64 => {
if (target.abi == .gnux32) {
return "elf32_x86_64";
}
// Any target elf will use the freebsd osabi if suffixed with "_fbsd".
if (target.os.tag == .freebsd) {
return "elf_x86_64_fbsd";
}
return "elf_x86_64";
},
.riscv32 => return "elf32lriscv",
.riscv64 => return "elf64lriscv",
else => return null,
}
}

View File

@ -14,6 +14,7 @@ const zir = @import("zir.zig");
const build_options = @import("build_options");
const warn = std.log.warn;
const introspect = @import("introspect.zig");
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.log.emerg(format, args);
@ -33,18 +34,22 @@ const usage =
\\
\\Commands:
\\
\\ build-exe [source] Create executable from source or object files
\\ build-lib [source] Create library from source or object files
\\ build-obj [source] Create object from source or assembly
\\ cc Use Zig as a drop-in C compiler
\\ c++ Use Zig as a drop-in C++ compiler
\\ env Print lib path, std path, compiler id and version
\\ fmt [source] Parse file and render in canonical zig format
\\ translate-c [source] Convert C code to Zig code
\\ targets List available compilation targets
\\ version Print version number and exit
\\ zen Print zen of zig and exit
\\ build-exe Create executable from source or object files
\\ build-lib Create library from source or object files
\\ build-obj Create object from source or assembly
\\ cc Use Zig as a drop-in C compiler
\\ c++ Use Zig as a drop-in C++ compiler
\\ env Print lib path, std path, compiler id and version
\\ fmt Parse file and render in canonical zig format
\\ libc Display native libc paths file or validate one
\\ translate-c Convert C code to Zig code
\\ targets List available compilation targets
\\ version Print version number and exit
\\ zen Print zen of zig and exit
\\
\\General Options:
\\
\\ --help Print command-specific usage
\\
;
@ -126,6 +131,8 @@ pub fn main() !void {
return punt_to_clang(arena, args);
} else if (mem.eql(u8, cmd, "fmt")) {
return cmdFmt(gpa, cmd_args);
} else if (mem.eql(u8, cmd, "libc")) {
return cmdLibC(gpa, cmd_args);
} else if (mem.eql(u8, cmd, "targets")) {
const info = try std.zig.system.NativeTargetInfo.detect(arena, .{});
const stdout = io.getStdOut().outStream();
@ -184,7 +191,6 @@ const usage_build_generic =
\\ ReleaseSmall Optimize for small binary, safety off
\\ -fPIC Force-enable Position Independent Code
\\ -fno-PIC Force-disable Position Independent Code
\\ --dynamic Force output to be dynamically linked
\\ --strip Exclude debug symbols
\\ -ofmt=[mode] Override target object format
\\ elf Executable and Linking Format
@ -199,6 +205,7 @@ const usage_build_generic =
\\ -isystem [dir] Add directory to SYSTEM include search path
\\ -I[dir] Add directory to include search path
\\ -D[macro]=[value] Define C [macro] to [value] (1 if [value] omitted)
\\ --libc [file] Provide a file which specifies libc paths
\\
\\Link Options:
\\ -l[lib], --library [lib] Link against system library
@ -208,6 +215,9 @@ const usage_build_generic =
\\ --version [ver] Dynamic library semver
\\ -rdynamic Add all symbols to the dynamic symbol table
\\ -rpath [path] Add directory to the runtime library search path
\\ --eh-frame-hdr Enable C++ exception handling by passing --eh-frame-hdr to linker
\\ -dynamic Force output to be dynamically linked
\\ -static Force output to be statically linked
\\
\\Debug Options (Zig Compiler Development):
\\ -ftime-report Print timing diagnostics
@ -220,6 +230,14 @@ const usage_build_generic =
\\
;
const repl_help =
\\Commands:
\\ update Detect changes to source files and update output files.
\\ help Print this text
\\ exit Quit this repl
\\
;
const Emit = union(enum) {
no,
yes_default_path,
@ -275,16 +293,17 @@ pub fn buildOutputType(
var version_script: ?[]const u8 = null;
var disable_c_depfile = false;
var override_soname: ?[]const u8 = null;
var linker_optimization: ?[]const u8 = null;
var linker_gc_sections: ?bool = null;
var linker_allow_shlib_undefined: ?bool = null;
var linker_bind_global_refs_locally: ?bool = null;
var linker_z_nodelete = false;
var linker_z_defs = false;
var stack_size_override: u64 = 0;
var stack_size_override: ?u64 = null;
var use_llvm: ?bool = null;
var use_lld: ?bool = null;
var use_clang: ?bool = null;
var link_eh_frame_hdr = false;
var libc_paths_file: ?[]const u8 = null;
var system_libs = std.ArrayList([]const u8).init(gpa);
defer system_libs.deinit();
@ -292,6 +311,9 @@ pub fn buildOutputType(
var clang_argv = std.ArrayList([]const u8).init(gpa);
defer clang_argv.deinit();
var lld_argv = std.ArrayList([]const u8).init(gpa);
defer lld_argv.deinit();
var lib_dirs = std.ArrayList([]const u8).init(gpa);
defer lib_dirs.deinit();
@ -414,15 +436,11 @@ pub fn buildOutputType(
fatal("unable to parse --version '{}': {}", .{ args[i], @errorName(err) });
};
} else if (mem.eql(u8, arg, "-target")) {
if (i + 1 >= args.len) {
fatal("expected parameter after -target", .{});
}
if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
i += 1;
target_arch_os_abi = args[i];
} else if (mem.eql(u8, arg, "-mcpu")) {
if (i + 1 >= args.len) {
fatal("expected parameter after -mcpu", .{});
}
if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
i += 1;
target_mcpu = args[i];
} else if (mem.startsWith(u8, arg, "-ofmt=")) {
@ -430,11 +448,13 @@ pub fn buildOutputType(
} else if (mem.startsWith(u8, arg, "-mcpu=")) {
target_mcpu = arg["-mcpu=".len..];
} else if (mem.eql(u8, arg, "--dynamic-linker")) {
if (i + 1 >= args.len) {
fatal("expected parameter after --dynamic-linker", .{});
}
if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
i += 1;
target_dynamic_linker = args[i];
} else if (mem.eql(u8, arg, "--libc")) {
if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
i += 1;
libc_paths_file = args[i];
} else if (mem.eql(u8, arg, "--watch")) {
watch = true;
} else if (mem.eql(u8, arg, "-ftime-report")) {
@ -481,6 +501,8 @@ pub fn buildOutputType(
link_mode = .Static;
} else if (mem.eql(u8, arg, "--strip")) {
strip = true;
} else if (mem.eql(u8, arg, "--eh-frame-hdr")) {
link_eh_frame_hdr = true;
} else if (mem.eql(u8, arg, "-Bsymbolic")) {
linker_bind_global_refs_locally = true;
} else if (mem.eql(u8, arg, "--debug-tokenize")) {
@ -565,7 +587,7 @@ pub fn buildOutputType(
const file_ext = Module.classifyFileExt(mem.spanZ(it.only_arg));
switch (file_ext) {
.assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(it.only_arg),
.unknown => try link_objects.append(it.only_arg),
.unknown, .so => try link_objects.append(it.only_arg),
}
},
.l => {
@ -716,7 +738,7 @@ pub fn buildOutputType(
}
version_script = linker_args.items[i];
} else if (mem.startsWith(u8, arg, "-O")) {
linker_optimization = arg;
try lld_argv.append(arg);
} else if (mem.eql(u8, arg, "--gc-sections")) {
linker_gc_sections = true;
} else if (mem.eql(u8, arg, "--no-gc-sections")) {
@ -994,10 +1016,21 @@ pub fn buildOutputType(
};
var default_prng = std.rand.DefaultPrng.init(random_seed);
var libc_installation: ?LibCInstallation = null;
defer if (libc_installation) |*l| l.deinit(gpa);
if (libc_paths_file) |paths_file| {
libc_installation = LibCInstallation.parse(gpa, paths_file, io.getStdErr().writer()) catch |err| {
fatal("unable to parse libc paths file: {}", .{@errorName(err)});
};
}
const module = Module.create(gpa, .{
.zig_lib_dir = zig_lib_dir,
.root_name = root_name,
.target = target_info.target,
.is_native_os = cross_target.isNativeOs(),
.dynamic_linker = target_info.dynamic_linker.get(),
.output_mode = output_mode,
.root_pkg = root_pkg,
.bin_file_dir_path = null,
@ -1008,6 +1041,7 @@ pub fn buildOutputType(
.optimize_mode = build_mode,
.keep_source_files_loaded = zir_out_path != null,
.clang_argv = clang_argv.items,
.lld_argv = lld_argv.items,
.lib_dirs = lib_dirs.items,
.rpath_list = rpath_list.items,
.c_source_files = c_source_files.items,
@ -1028,17 +1062,19 @@ pub fn buildOutputType(
.version_script = version_script,
.disable_c_depfile = disable_c_depfile,
.override_soname = override_soname,
.linker_optimization = linker_optimization,
.linker_gc_sections = linker_gc_sections,
.linker_allow_shlib_undefined = linker_allow_shlib_undefined,
.linker_bind_global_refs_locally = linker_bind_global_refs_locally,
.linker_z_nodelete = linker_z_nodelete,
.linker_z_defs = linker_z_defs,
.link_eh_frame_hdr = link_eh_frame_hdr,
.stack_size_override = stack_size_override,
.strip = strip,
.self_exe_path = self_exe_path,
.rand = &default_prng.random,
.clang_passthrough_mode = arg_mode != .build,
.version = version,
.libc_installation = if (libc_installation) |*lci| lci else null,
}) catch |err| {
fatal("unable to create module: {}", .{@errorName(err)});
};
@ -1116,16 +1152,64 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo
}
}
const repl_help =
\\Commands:
\\ update Detect changes to source files and update output files.
\\ help Print this text
\\ exit Quit this repl
pub const usage_libc =
\\Usage: zig libc
\\
\\ Detect the native libc installation and print the resulting
\\ paths to stdout. You can save this into a file and then edit
\\ the paths to create a cross compilation libc kit. Then you
\\ can pass `--libc [file]` for Zig to use it.
\\
\\Usage: zig libc [paths_file]
\\
\\ Parse a libc installation text file and validate it.
\\
;
pub fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void {
var input_file: ?[]const u8 = null;
{
var i: usize = 0;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "--help")) {
const stdout = io.getStdOut().writer();
try stdout.writeAll(usage_libc);
process.exit(0);
} else {
fatal("unrecognized parameter: '{}'", .{arg});
}
} else if (input_file != null) {
fatal("unexpected extra parameter: '{}'", .{arg});
} else {
input_file = arg;
}
}
}
if (input_file) |libc_file| {
const stderr = std.io.getStdErr().writer();
var libc = LibCInstallation.parse(gpa, libc_file, stderr) catch |err| {
fatal("unable to parse libc file: {}", .{@errorName(err)});
};
defer libc.deinit(gpa);
} else {
var libc = LibCInstallation.findNative(.{
.allocator = gpa,
.verbose = true,
}) catch |err| {
fatal("unable to detect native libc: {}", .{@errorName(err)});
};
defer libc.deinit(gpa);
var bos = io.bufferedOutStream(io.getStdOut().writer());
try libc.render(bos.writer());
try bos.flush();
}
}
pub const usage_fmt =
\\usage: zig fmt [file]...
\\Usage: zig fmt [file]...
\\
\\ Formats the input files and modifies them in-place.
\\ Arguments can be files or directories, which are searched

View File

@ -109,3 +109,28 @@ pub fn canBuildLibC(target: std.Target) bool {
}
return false;
}
pub fn cannotDynamicLink(target: std.Target) bool {
return switch (target.os.tag) {
.freestanding, .other => true,
else => false,
};
}
pub fn osRequiresLibC(target: std.Target) bool {
// On Darwin, we always link libSystem which contains libc.
// Similarly on FreeBSD and NetBSD we always link system libc
// since this is the stable syscall interface.
return switch (target.os.tag) {
.freebsd, .netbsd, .dragonfly, .macosx, .ios, .watchos, .tvos => true,
else => false,
};
}
pub fn requiresPIE(target: std.Target) bool {
return target.isAndroid();
}
pub fn libc_needs_crti_crtn(target: std.Target) bool {
return !(target.cpu.arch.isRISCV() or target.isAndroid());
}

View File

@ -472,6 +472,7 @@ pub const TestContext = struct {
.root_pkg = root_pkg,
.keep_source_files_loaded = true,
.object_format = ofmt,
.is_native_os = case.target.isNativeOs(),
});
defer module.destroy();