Commit e05ecbf165
Changed files (7)
src-self-hosted
src-self-hosted/link/Elf.zig
@@ -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,
+ }
+}
src-self-hosted/libc_installation.zig
@@ -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,
src-self-hosted/link.zig
@@ -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;
}
src-self-hosted/main.zig
@@ -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
src-self-hosted/Module.zig
@@ -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,
+ };
+ }
+
+ 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,
+ };
+}
- // TODO finish porting detect_libc from codegen.cpp
- return error.LibCDetectionUnimplemented;
+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;
}
src-self-hosted/target.zig
@@ -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());
+}
src-self-hosted/test.zig
@@ -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();