diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 441ce15dc0..482a1d9a49 100644 --- a/src-self-hosted/Module.zig +++ b/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, + }; } - // 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; } diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index 65c6c8c16d..125f514060 100644 --- a/src-self-hosted/libc_installation.zig +++ b/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, diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 22c8a6c8e3..68292fbfe6 100644 --- a/src-self-hosted/link.zig +++ b/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; } diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 22ed0446c4..835ab12cfa 100644 --- a/src-self-hosted/link/Elf.zig +++ b/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, + } +} diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 81a3126c7f..1de025769d 100644 --- a/src-self-hosted/main.zig +++ b/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 diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig index cb09779d74..127abeeadc 100644 --- a/src-self-hosted/target.zig +++ b/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()); +} diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 004b56b029..f23695257b 100644 --- a/src-self-hosted/test.zig +++ b/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();