diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 715979bf11..4c48837d9e 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -25,6 +25,8 @@ 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; +const glibc = @import("glibc.zig"); +const fatal = @import("main.zig").fatal; /// General-purpose allocator. Used for both temporary and long-term storage. gpa: *Allocator, @@ -91,17 +93,20 @@ sanitize_c: bool, /// Otherwise we attempt to parse the error messages and expose them via the Module API. /// This is `true` for `zig cc`, `zig c++`, and `zig translate-c`. clang_passthrough_mode: bool, +/// Whether to print clang argvs to stdout. +debug_cc: bool, /// Error tags and their values, tag names are duped with mod.gpa. global_error_set: std.StringHashMapUnmanaged(u16) = .{}, -c_source_files: []const []const u8, +c_source_files: []const CSourceFile, clang_argv: []const []const u8, cache: std.cache_hash.CacheHash, /// Path to own executable for invoking `zig clang`. self_exe_path: ?[]const u8, -zig_lib_dir: []const u8, -zig_cache_dir_path: []const u8, +zig_lib_directory: Directory, +zig_cache_directory: Directory, +zig_cache_artifact_directory: Directory, libc_include_dir_list: []const []const u8, rand: *std.rand.Random, @@ -118,8 +123,19 @@ libunwind_static_lib: ?[]const u8 = null, /// and resolved before calling linker.flush(). libc_static_lib: ?[]const u8 = null, +/// For example `Scrt1.o` and `libc.so.6`. These are populated after building libc from source, +/// The set of needed CRT (C runtime) files differs depending on the target and compilation settings. +/// The key is the basename, and the value is the absolute path to the completed build artifact. +crt_files: std.StringHashMapUnmanaged([]const u8) = .{}, + pub const InnerError = error{ OutOfMemory, AnalysisFail }; +/// For passing to a C compiler. +pub const CSourceFile = struct { + src_path: []const u8, + extra_flags: []const []const u8 = &[0][]const u8{}, +}; + const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Decl, @@ -133,6 +149,11 @@ const WorkItem = union(enum) { /// Invoke the Clang compiler to create an object file, which gets linked /// with the Module. c_object: *CObject, + + /// one of the glibc static objects + glibc_crt_file: glibc.CRTFile, + /// one of the glibc shared objects + glibc_so: *const glibc.Lib, }; pub const Export = struct { @@ -701,7 +722,7 @@ pub const Scope = struct { pub fn getSource(self: *File, module: *Module) ![:0]const u8 { switch (self.source) { .unloaded => { - const source = try module.root_pkg.?.root_src_dir.readFileAllocOptions( + const source = try module.root_pkg.?.root_src_directory.handle.readFileAllocOptions( module.gpa, self.sub_file_path, std.math.maxInt(u32), @@ -805,7 +826,7 @@ pub const Scope = struct { pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 { switch (self.source) { .unloaded => { - const source = try module.root_pkg.?.root_src_dir.readFileAllocOptions( + const source = try module.root_pkg.?.root_src_directory.handle.readFileAllocOptions( module.gpa, self.sub_file_path, std.math.maxInt(u32), @@ -937,18 +958,35 @@ pub const AllErrors = struct { } }; +pub const Directory = struct { + /// This field is redundant for operations that can act on the open directory handle + /// directly, but it is needed when passing the directory to a child process. + /// `null` means cwd. + path: ?[]const u8, + handle: std.fs.Dir, +}; + +pub const EmitLoc = struct { + /// If this is `null` it means the file will be output to the cache directory. + /// When provided, both the open file handle and the path name must outlive the `Module`. + directory: ?Module.Directory, + /// This may not have sub-directories in it. + basename: []const u8, +}; + pub const InitOptions = struct { - zig_lib_dir: []const u8, + zig_lib_directory: Directory, + zig_cache_directory: Directory, target: Target, root_name: []const u8, 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, - emit_h: ?[]const u8 = null, + /// `null` means to not emit a binary file. + emit_bin: ?EmitLoc, + /// `null` means to not emit a C header file. + emit_h: ?EmitLoc = null, link_mode: ?std.builtin.LinkMode = null, object_format: ?std.builtin.ObjectFormat = null, optimize_mode: std.builtin.Mode = .Debug, @@ -957,7 +995,7 @@ pub const InitOptions = struct { 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{}, + c_source_files: []const CSourceFile = &[0]CSourceFile{}, link_objects: []const []const u8 = &[0][]const u8{}, framework_dirs: []const []const u8 = &[0][]const u8{}, frameworks: []const []const u8 = &[0][]const u8{}, @@ -966,11 +1004,14 @@ pub const InitOptions = struct { link_libcpp: bool = false, want_pic: ?bool = null, want_sanitize_c: ?bool = null, + want_stack_check: ?bool = null, + want_valgrind: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, use_clang: ?bool = null, rdynamic: bool = false, strip: bool = false, + single_threaded: bool = false, is_native_os: bool, link_eh_frame_hdr: bool = false, linker_script: ?[]const u8 = null, @@ -984,6 +1025,8 @@ pub const InitOptions = struct { linker_z_nodelete: bool = false, linker_z_defs: bool = false, clang_passthrough_mode: bool = false, + debug_cc: bool = false, + debug_link: bool = false, stack_size_override: ?u64 = null, self_exe_path: ?[]const u8 = null, version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 }, @@ -1057,17 +1100,126 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { const libc_dirs = try detectLibCIncludeDirs( arena, - options.zig_lib_dir, + options.zig_lib_directory.path.?, options.target, options.is_native_os, options.link_libc, options.libc_installation, ); + const must_pic: bool = b: { + if (target_util.requiresPIC(options.target, options.link_libc)) + break :b true; + break :b link_mode == .Dynamic; + }; + const pic = options.want_pic orelse must_pic; + + if (options.emit_h != null) fatal("-femit-h not supported yet", .{}); // TODO + + const emit_bin = options.emit_bin orelse fatal("-fno-emit-bin not supported yet", .{}); // TODO + + // Make a decision on whether to use Clang for translate-c and compiling C files. + const use_clang = if (options.use_clang) |explicit| explicit else blk: { + if (build_options.have_llvm) { + // Can't use it if we don't have it! + break :blk false; + } + // It's not planned to do our own translate-c or C compilation. + break :blk true; + }; + + const is_safe_mode = switch (options.optimize_mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, + }; + + const sanitize_c = options.want_sanitize_c orelse is_safe_mode; + + const stack_check: bool = b: { + if (!target_util.supportsStackProbing(options.target)) + break :b false; + break :b options.want_stack_check orelse is_safe_mode; + }; + + const valgrind: bool = b: { + if (!target_util.hasValgrindSupport(options.target)) + break :b false; + break :b options.want_valgrind orelse (options.optimize_mode == .Debug); + }; + + const single_threaded = options.single_threaded or target_util.isSingleThreaded(options.target); + + // We put everything into the cache hash that *cannot be modified during an incremental update*. + // For example, one cannot change the target between updates, but one can change source files, + // so the target goes into the cache hash, but source files do not. This is so that we can + // find the same binary and incrementally update it even if there are modified source files. + // We do this even if outputting to the current directory because (1) this cache_hash instance + // will be the "parent" of other cache_hash instances such as for C objects, (2) we need + // a place for intermediate build artifacts, such as a .o file to be linked with LLD, and (3) + // we need somewhere to store serialization of incremental compilation metadata. + var cache = try std.cache_hash.CacheHash.init(gpa, options.zig_cache_directory.handle, "h"); + errdefer cache.release(); + + // Now we will prepare hash state initializations to avoid redundantly computing hashes. + // First we add common things between things that apply to zig source and all c source files. + cache.addBytes(build_options.version); + cache.add(options.optimize_mode); + cache.add(options.target.cpu.arch); + cache.addBytes(options.target.cpu.model.name); + cache.add(options.target.cpu.features.ints); + cache.add(options.target.os.tag); + switch (options.target.os.tag) { + .linux => { + cache.add(options.target.os.version_range.linux.range.min); + cache.add(options.target.os.version_range.linux.range.max); + cache.add(options.target.os.version_range.linux.glibc); + }, + .windows => { + cache.add(options.target.os.version_range.windows.min); + cache.add(options.target.os.version_range.windows.max); + }, + .freebsd, + .macosx, + .ios, + .tvos, + .watchos, + .netbsd, + .openbsd, + .dragonfly, + => { + cache.add(options.target.os.version_range.semver.min); + cache.add(options.target.os.version_range.semver.max); + }, + else => {}, + } + cache.add(options.target.abi); + cache.add(ofmt); + cache.add(pic); + cache.add(stack_check); + cache.add(sanitize_c); + cache.add(valgrind); + cache.add(link_mode); + cache.add(options.strip); + cache.add(single_threaded); + // TODO audit this and make sure everything is in it + + // We don't care whether we find something there, just show us the digest. + const digest = (try cache.hit()) orelse cache.final(); + + const artifact_sub_dir = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); + var artifact_dir = try options.zig_cache_directory.handle.makeOpenPath(artifact_sub_dir, .{}); + errdefer artifact_dir.close(); + const zig_cache_artifact_directory: Directory = .{ + .handle = artifact_dir, + .path = if (options.zig_cache_directory.path) |p| + try std.fs.path.join(arena, &[_][]const u8{ p, artifact_sub_dir }) + else + artifact_sub_dir, + }; + const bin_file = try link.File.openPath(gpa, .{ - .dir = options.bin_file_dir orelse std.fs.cwd(), - .dir_path = options.bin_file_dir_path, - .sub_path = options.bin_file_path, + .directory = emit_bin.directory orelse zig_cache_artifact_directory, + .sub_path = emit_bin.basename, .root_name = root_name, .root_pkg = options.root_pkg, .target = options.target, @@ -1103,6 +1255,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { .override_soname = options.override_soname, .version = options.version, .libc_installation = libc_dirs.libc_installation, + .pic = pic, + .valgrind = valgrind, + .stack_check = stack_check, + .single_threaded = single_threaded, + .debug_link = options.debug_link, }); errdefer bin_file.destroy(); @@ -1142,83 +1299,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { } }; - // We put everything into the cache hash except for the root source file, because we want to - // find the same binary and incrementally update it even if the file contents changed. - // TODO Look into storing this information in memory rather than on disk and solving - // serialization/deserialization of *all* incremental compilation state in a more generic way. - const cache_parent_dir = if (options.root_pkg) |root_pkg| root_pkg.root_src_dir else std.fs.cwd(); - var cache_dir = try cache_parent_dir.makeOpenPath("zig-cache", .{}); - defer cache_dir.close(); - - try cache_dir.makePath("tmp"); - try cache_dir.makePath("o"); - // We need this string because of sending paths to clang as a child process. - const zig_cache_dir_path = if (options.root_pkg) |root_pkg| - try std.fmt.allocPrint(arena, "{}" ++ std.fs.path.sep_str ++ "zig-cache", .{root_pkg.root_src_dir_path}) - else - "zig-cache"; - - var cache = try std.cache_hash.CacheHash.init(gpa, cache_dir, "h"); - errdefer cache.release(); - - // Now we will prepare hash state initializations to avoid redundantly computing hashes. - // First we add common things between things that apply to zig source and all c source files. - cache.addBytes(build_options.version); - cache.add(options.optimize_mode); - cache.add(options.target.cpu.arch); - cache.addBytes(options.target.cpu.model.name); - cache.add(options.target.cpu.features.ints); - cache.add(options.target.os.tag); - switch (options.target.os.tag) { - .linux => { - cache.add(options.target.os.version_range.linux.range.min); - cache.add(options.target.os.version_range.linux.range.max); - cache.add(options.target.os.version_range.linux.glibc); - }, - .windows => { - cache.add(options.target.os.version_range.windows.min); - cache.add(options.target.os.version_range.windows.max); - }, - .freebsd, - .macosx, - .ios, - .tvos, - .watchos, - .netbsd, - .openbsd, - .dragonfly, - => { - cache.add(options.target.os.version_range.semver.min); - cache.add(options.target.os.version_range.semver.max); - }, - else => {}, - } - cache.add(options.target.abi); - cache.add(ofmt); - // TODO PIC (see detect_pic from codegen.cpp) - cache.add(bin_file.options.link_mode); - cache.add(options.strip); - - // Make a decision on whether to use Clang for translate-c and compiling C files. - const use_clang = if (options.use_clang) |explicit| explicit else blk: { - if (build_options.have_llvm) { - // Can't use it if we don't have it! - break :blk false; - } - // It's not planned to do our own translate-c or C compilation. - break :blk true; - }; - - const sanitize_c: bool = options.want_sanitize_c orelse switch (options.optimize_mode) { - .Debug, .ReleaseSafe => true, - .ReleaseSmall, .ReleaseFast => false, - }; - mod.* = .{ .gpa = gpa, .arena_state = arena_allocator.state, - .zig_lib_dir = options.zig_lib_dir, - .zig_cache_dir_path = zig_cache_dir_path, + .zig_lib_directory = options.zig_lib_directory, + .zig_cache_directory = options.zig_cache_directory, + .zig_cache_artifact_directory = zig_cache_artifact_directory, .root_pkg = options.root_pkg, .root_scope = root_scope, .bin_file = bin_file, @@ -1233,6 +1319,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { .sanitize_c = sanitize_c, .rand = options.rand, .clang_passthrough_mode = options.clang_passthrough_mode, + .debug_cc = options.debug_cc, }; break :mod mod; }; @@ -1245,22 +1332,21 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { errdefer local_arena.deinit(); const c_object = try local_arena.allocator.create(CObject); - const src_path = try local_arena.allocator.dupe(u8, c_source_file); c_object.* = .{ .status = .{ .new = {} }, - .src_path = src_path, - .extra_flags = &[0][]const u8{}, + // TODO why are we duplicating this memory? do we need to? + // look into refactoring to turn these 2 fields simply into a CSourceFile + .src_path = try local_arena.allocator.dupe(u8, c_source_file.src_path), + .extra_flags = try local_arena.allocator.dupe([]const u8, c_source_file.extra_flags), .arena = local_arena.state, }; mod.c_object_table.putAssumeCapacityNoClobber(c_object, {}); } // If we need to build glibc for the target, add work items for it. - if (mod.bin_file.options.link_libc and - mod.bin_file.options.libc_installation == null and - mod.bin_file.options.target.isGnuLibC()) - { + // We go through the work queue so that building can be done in parallel. + if (mod.wantBuildGLibCFromSource()) { try mod.addBuildingGLibCWorkItems(); } @@ -1273,6 +1359,15 @@ pub fn destroy(self: *Module) void { self.deletion_set.deinit(gpa); self.work_queue.deinit(); + { + var it = self.crt_files.iterator(); + while (it.next()) |entry| { + gpa.free(entry.key); + gpa.free(entry.value); + } + self.crt_files.deinit(gpa); + } + for (self.decl_table.items()) |entry| { entry.value.destroy(gpa); } @@ -1322,6 +1417,8 @@ pub fn destroy(self: *Module) void { gpa.free(entry.key); } self.global_error_set.deinit(gpa); + + self.zig_cache_artifact_directory.handle.close(); self.cache.release(); // This destroys `self`. @@ -1458,7 +1555,7 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors { if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) { const global_err_src_path = blk: { if (self.root_pkg) |root_pkg| break :blk root_pkg.root_src_path; - if (self.c_source_files.len != 0) break :blk self.c_source_files[0]; + if (self.c_source_files.len != 0) break :blk self.c_source_files[0].src_path; if (self.bin_file.options.objects.len != 0) break :blk self.bin_file.options.objects[0]; break :blk "(no file)"; }; @@ -1581,6 +1678,17 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { }, }; }, + .glibc_crt_file => |crt_file| { + glibc.buildCRTFile(self, crt_file) catch |err| { + // This is a problem with the Zig installation. It's mostly OK to crash here, + // but TODO because it would be even better if we could recover gracefully + // from temporary problems such as out-of-disk-space. + fatal("unable to build glibc CRT file: {}", .{@errorName(err)}); + }; + }, + .glibc_so => |glibc_lib| { + fatal("TODO build glibc shared object '{}.so.{}'", .{ glibc_lib.name, glibc_lib.sover }); + }, }; } @@ -1588,6 +1696,8 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void { const tracy = trace(@src()); defer tracy.end(); + // TODO this C source file needs its own cache hash instance + if (!build_options.have_llvm) { return mod.failCObj(c_object, "clang not available: compiler not built with LLVM extensions enabled", .{}); } @@ -1616,6 +1726,9 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void { // We can't know the digest until we do the C compiler invocation, so we need a temporary filename. const out_obj_path = try mod.tmpFilePath(arena, o_basename); + var zig_cache_tmp_dir = try mod.zig_cache_directory.handle.makeOpenPath("tmp", .{}); + defer zig_cache_tmp_dir.close(); + try argv.appendSlice(&[_][]const u8{ self_exe_path, "clang", "-c" }); const ext = classifyFileExt(c_object.src_path); @@ -1628,9 +1741,12 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void { try argv.append(c_object.src_path); try argv.appendSlice(c_object.extra_flags); - //for (argv.items) |arg| { - // std.debug.print("{} ", .{arg}); - //} + if (mod.debug_cc) { + for (argv.items[0 .. argv.items.len - 1]) |arg| { + std.debug.print("{} ", .{arg}); + } + std.debug.print("{}\n", .{argv.items[argv.items.len - 1]}); + } const child = try std.ChildProcess.init(argv.items, arena); defer child.deinit(); @@ -1689,11 +1805,13 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void { // TODO handle .d files - // TODO rename into place - std.debug.print("TODO rename {} into cache dir\n", .{out_obj_path}); + // TODO Add renameat capabilities to the std lib in a higher layer than the posix layer. + const tmp_basename = std.fs.path.basename(out_obj_path); + try std.os.renameat(zig_cache_tmp_dir.fd, tmp_basename, mod.zig_cache_artifact_directory.handle.fd, o_basename); - // TODO use the cache file name instead of tmp file name - const success_file_path = try mod.gpa.dupe(u8, out_obj_path); + const success_file_path = try std.fs.path.join(mod.gpa, &[_][]const u8{ + mod.zig_cache_artifact_directory.path.?, o_basename, + }); c_object.status = .{ .success = success_file_path }; } @@ -1702,7 +1820,7 @@ fn tmpFilePath(mod: *Module, arena: *Allocator, suffix: []const u8) error{OutOfM return std.fmt.allocPrint( arena, "{}" ++ s ++ "tmp" ++ s ++ "{x}-{}", - .{ mod.zig_cache_dir_path, mod.rand.int(u64), suffix }, + .{ mod.zig_cache_directory.path.?, mod.rand.int(u64), suffix }, ); } @@ -1749,10 +1867,10 @@ fn addCCArgs( if (mod.bin_file.options.link_libcpp) { const libcxx_include_path = try std.fs.path.join(arena, &[_][]const u8{ - mod.zig_lib_dir, "libcxx", "include", + mod.zig_lib_directory.path.?, "libcxx", "include", }); const libcxxabi_include_path = try std.fs.path.join(arena, &[_][]const u8{ - mod.zig_lib_dir, "libcxxabi", "include", + mod.zig_lib_directory.path.?, "libcxxabi", "include", }); try argv.append("-isystem"); @@ -1776,7 +1894,7 @@ fn addCCArgs( // According to Rich Felker libc headers are supposed to go before C language headers. // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics // and other compiler specific items. - const c_headers_dir = try std.fs.path.join(arena, &[_][]const u8{ mod.zig_lib_dir, "include" }); + const c_headers_dir = try std.fs.path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, "include" }); try argv.append("-isystem"); try argv.append(c_headers_dir); @@ -1891,10 +2009,9 @@ fn addCCArgs( }, } - // TODO add CLI args for PIC - //if (target_supports_fpic(g->zig_target) and g->have_pic) { - // try argv.append("-fPIC"); - //} + if (target_util.supports_fpic(target) and mod.bin_file.options.pic) { + try argv.append("-fPIC"); + } try argv.appendSlice(mod.clang_argv); } @@ -4497,7 +4614,9 @@ fn detectLibCFromLibCInstallation(arena: *Allocator, target: Target, lci: *const } pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 { - // TODO port support for building crt files from stage1 + if (mod.wantBuildGLibCFromSource()) { + return mod.crt_files.get(basename).?; + } 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 }); @@ -4505,6 +4624,23 @@ pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8) } fn addBuildingGLibCWorkItems(mod: *Module) !void { - // crti.o, crtn.o, start.os, abi-note.o, Scrt1.o, libc_nonshared.a - try mod.work_queue.ensureUnusedCapacity(6); + const static_file_work_items = [_]WorkItem{ + .{ .glibc_crt_file = .crti_o }, + .{ .glibc_crt_file = .crtn_o }, + .{ .glibc_crt_file = .start_os }, + .{ .glibc_crt_file = .abi_note_o }, + .{ .glibc_crt_file = .scrt1_o }, + .{ .glibc_crt_file = .libc_nonshared_a }, + }; + try mod.work_queue.ensureUnusedCapacity(static_file_work_items.len + glibc.libs.len); + mod.work_queue.writeAssumeCapacity(&static_file_work_items); + for (glibc.libs) |*glibc_so| { + mod.work_queue.writeItemAssumeCapacity(.{ .glibc_so = glibc_so }); + } +} + +fn wantBuildGLibCFromSource(mod: *Module) bool { + return mod.bin_file.options.link_libc and + mod.bin_file.options.libc_installation == null and + mod.bin_file.options.target.isGnuLibC(); } diff --git a/src-self-hosted/Package.zig b/src-self-hosted/Package.zig index 4bf4defbc8..11b1436293 100644 --- a/src-self-hosted/Package.zig +++ b/src-self-hosted/Package.zig @@ -1,59 +1,59 @@ -pub const Table = std.StringHashMap(*Package); +pub const Table = std.StringHashMapUnmanaged(*Package); -/// This should be used for file operations. -root_src_dir: std.fs.Dir, -/// This is for metadata purposes, for example putting into debug information. -root_src_dir_path: []u8, -/// Relative to `root_src_dir` and `root_src_dir_path`. +root_src_directory: Module.Directory, +/// Relative to `root_src_directory`. root_src_path: []u8, table: Table, /// No references to `root_src_dir` and `root_src_path` are kept. pub fn create( - allocator: *mem.Allocator, + gpa: *Allocator, base_dir: std.fs.Dir, /// Relative to `base_dir`. root_src_dir: []const u8, /// Relative to `root_src_dir`. root_src_path: []const u8, ) !*Package { - const ptr = try allocator.create(Package); - errdefer allocator.destroy(ptr); - const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path); - errdefer allocator.free(root_src_path_dupe); - const root_src_dir_path = try mem.dupe(allocator, u8, root_src_dir); - errdefer allocator.free(root_src_dir_path); + const ptr = try gpa.create(Package); + errdefer gpa.destroy(ptr); + const root_src_path_dupe = try mem.dupe(gpa, u8, root_src_path); + errdefer gpa.free(root_src_path_dupe); + const root_src_dir_path = try mem.dupe(gpa, u8, root_src_dir); + errdefer gpa.free(root_src_dir_path); ptr.* = .{ - .root_src_dir = try base_dir.openDir(root_src_dir, .{}), - .root_src_dir_path = root_src_dir_path, + .root_src_directory = .{ + .path = root_src_dir_path, + .handle = try base_dir.openDir(root_src_dir, .{}), + }, .root_src_path = root_src_path_dupe, - .table = Table.init(allocator), + .table = .{}, }; return ptr; } -pub fn destroy(self: *Package) void { - const allocator = self.table.allocator; - self.root_src_dir.close(); - allocator.free(self.root_src_path); - allocator.free(self.root_src_dir_path); +pub fn destroy(pkg: *Package, gpa: *Allocator) void { + pkg.root_src_directory.handle.close(); + gpa.free(pkg.root_src_path); + if (pkg.root_src_directory.path) |p| gpa.free(p); { - var it = self.table.iterator(); + var it = pkg.table.iterator(); while (it.next()) |kv| { - allocator.free(kv.key); + gpa.free(kv.key); } } - self.table.deinit(); - allocator.destroy(self); + pkg.table.deinit(gpa); + gpa.destroy(pkg); } -pub fn add(self: *Package, name: []const u8, package: *Package) !void { - try self.table.ensureCapacity(self.table.items().len + 1); - const name_dupe = try mem.dupe(self.table.allocator, u8, name); - self.table.putAssumeCapacityNoClobber(name_dupe, package); +pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void { + try pkg.table.ensureCapacity(gpa, pkg.table.items().len + 1); + const name_dupe = try mem.dupe(gpa, u8, name); + pkg.table.putAssumeCapacityNoClobber(name_dupe, package); } const std = @import("std"); const mem = std.mem; +const Allocator = std.mem.Allocator; const assert = std.debug.assert; const Package = @This(); +const Module = @import("Module.zig"); diff --git a/src-self-hosted/glibc.zig b/src-self-hosted/glibc.zig index ea429a69a3..9d0a0d45fd 100644 --- a/src-self-hosted/glibc.zig +++ b/src-self-hosted/glibc.zig @@ -2,6 +2,9 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const target_util = @import("target.zig"); const mem = std.mem; +const Module = @import("Module.zig"); +const path = std.fs.path; +const build_options = @import("build_options"); pub const Lib = struct { name: []const u8, @@ -60,7 +63,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! var version_table = std.AutoHashMapUnmanaged(target_util.ArchOsAbi, [*]VerList){}; errdefer version_table.deinit(gpa); - var glibc_dir = zig_lib_dir.openDir("libc" ++ std.fs.path.sep_str ++ "glibc", .{}) catch |err| { + var glibc_dir = zig_lib_dir.openDir("libc" ++ path.sep_str ++ "glibc", .{}) catch |err| { std.log.err("unable to open glibc dir: {}", .{@errorName(err)}); return error.ZigInstallationCorrupt; }; @@ -229,3 +232,394 @@ fn findLib(name: []const u8) ?*const Lib { } return null; } + +pub const CRTFile = enum { + crti_o, + crtn_o, + start_os, + abi_note_o, + scrt1_o, + libc_nonshared_a, +}; + +pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void { + if (!build_options.have_llvm) { + return error.ZigCompilerNotBuiltWithLLVMExtensions; + } + const gpa = mod.gpa; + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + errdefer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + switch (crt_file) { + .crti_o => { + var args = std.ArrayList([]const u8).init(arena); + try add_include_dirs(mod, arena, &args); + try args.appendSlice(&[_][]const u8{ + "-D_LIBC_REENTRANT", + "-include", + try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"), + "-DMODULE_NAME=libc", + "-Wno-nonportable-include-path", + "-include", + try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"), + "-DTOP_NAMESPACE=glibc", + "-DASSEMBLER", + "-g", + "-Wa,--noexecstack", + }); + const c_source_file: Module.CSourceFile = .{ + .src_path = try start_asm_path(mod, arena, "crti.S"), + .extra_flags = args.items, + }; + return build_libc_object(mod, "crti.o", c_source_file); + }, + .crtn_o => { + var args = std.ArrayList([]const u8).init(arena); + try add_include_dirs(mod, arena, &args); + try args.appendSlice(&[_][]const u8{ + "-D_LIBC_REENTRANT", + "-DMODULE_NAME=libc", + "-DTOP_NAMESPACE=glibc", + "-DASSEMBLER", + "-g", + "-Wa,--noexecstack", + }); + const c_source_file: Module.CSourceFile = .{ + .src_path = try start_asm_path(mod, arena, "crtn.S"), + .extra_flags = args.items, + }; + return build_libc_object(mod, "crtn.o", c_source_file); + }, + .start_os => { + var args = std.ArrayList([]const u8).init(arena); + try add_include_dirs(mod, arena, &args); + try args.appendSlice(&[_][]const u8{ + "-D_LIBC_REENTRANT", + "-include", + try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"), + "-DMODULE_NAME=libc", + "-Wno-nonportable-include-path", + "-include", + try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"), + "-DPIC", + "-DSHARED", + "-DTOP_NAMESPACE=glibc", + "-DASSEMBLER", + "-g", + "-Wa,--noexecstack", + }); + const c_source_file: Module.CSourceFile = .{ + .src_path = try start_asm_path(mod, arena, "start.S"), + .extra_flags = args.items, + }; + return build_libc_object(mod, "start.os", c_source_file); + }, + .abi_note_o => { + var args = std.ArrayList([]const u8).init(arena); + try args.appendSlice(&[_][]const u8{ + "-I", + try lib_path(mod, arena, lib_libc_glibc ++ "glibc" ++ path.sep_str ++ "csu"), + }); + try add_include_dirs(mod, arena, &args); + try args.appendSlice(&[_][]const u8{ + "-D_LIBC_REENTRANT", + "-DMODULE_NAME=libc", + "-DTOP_NAMESPACE=glibc", + "-DASSEMBLER", + "-g", + "-Wa,--noexecstack", + }); + const c_source_file: Module.CSourceFile = .{ + .src_path = try lib_path(mod, arena, lib_libc_glibc ++ "csu" ++ path.sep_str ++ "abi-note.S"), + .extra_flags = args.items, + }; + return build_libc_object(mod, "abi-note.o", c_source_file); + }, + .scrt1_o => { + return error.Unimplemented; // TODO + }, + .libc_nonshared_a => { + return error.Unimplemented; // TODO + }, + } +} + +fn start_asm_path(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 { + const arch = mod.getTarget().cpu.arch; + const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; + const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; + const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparcv9; + const is_64 = arch.ptrBitWidth() == 64; + + const s = path.sep_str; + + var result = std.ArrayList(u8).init(arena); + try result.appendSlice(mod.zig_lib_directory.path.?); + try result.appendSlice(s ++ "libc" ++ s ++ "glibc" ++ s ++ "sysdeps" ++ s); + if (is_sparc) { + if (is_64) { + try result.appendSlice("sparc" ++ s ++ "sparc64"); + } else { + try result.appendSlice("sparc" ++ s ++ "sparc32"); + } + } else if (arch.isARM()) { + try result.appendSlice("arm"); + } else if (arch.isMIPS()) { + try result.appendSlice("mips"); + } else if (arch == .x86_64) { + try result.appendSlice("x86_64"); + } else if (arch == .i386) { + try result.appendSlice("i386"); + } else if (is_aarch64) { + try result.appendSlice("aarch64"); + } else if (arch.isRISCV()) { + try result.appendSlice("riscv"); + } else if (is_ppc) { + if (is_64) { + try result.appendSlice("powerpc" ++ s ++ "powerpc64"); + } else { + try result.appendSlice("powerpc" ++ s ++ "powerpc32"); + } + } + + try result.appendSlice(s); + try result.appendSlice(basename); + return result.items; +} + +fn add_include_dirs(mod: *Module, arena: *Allocator, args: *std.ArrayList([]const u8)) error{OutOfMemory}!void { + const target = mod.getTarget(); + const arch = target.cpu.arch; + const opt_nptl: ?[]const u8 = if (target.os.tag == .linux) "nptl" else "htl"; + const glibc = try lib_path(mod, arena, lib_libc ++ "glibc"); + + const s = path.sep_str; + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "include")); + + if (target.os.tag == .linux) { + try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux")); + } + + if (opt_nptl) |nptl| { + try add_include_dirs_arch(arena, args, arch, nptl, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps")); + } + + if (target.os.tag == .linux) { + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ + "unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "generic")); + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ + "unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "include")); + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ + "unix" ++ s ++ "sysv" ++ s ++ "linux")); + } + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, lib_libc_glibc ++ "sysdeps", nptl })); + } + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "pthread")); + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv")); + + try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix")); + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix")); + + try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps")); + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "generic")); + + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, lib_libc ++ "glibc" })); + + try args.append("-I"); + try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-{}", .{ + mod.zig_lib_directory.path.?, @tagName(arch), @tagName(target.os.tag), @tagName(target.abi), + })); + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc ++ "include" ++ s ++ "generic-glibc")); + + try args.append("-I"); + try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-linux-any", .{ + mod.zig_lib_directory.path.?, @tagName(arch), + })); + + try args.append("-I"); + try args.append(try lib_path(mod, arena, lib_libc ++ "include" ++ s ++ "any-linux-any")); +} + +fn add_include_dirs_arch( + arena: *Allocator, + args: *std.ArrayList([]const u8), + arch: std.Target.Cpu.Arch, + opt_nptl: ?[]const u8, + dir: []const u8, +) error{OutOfMemory}!void { + const is_x86 = arch == .i386 or arch == .x86_64; + const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; + const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; + const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparcv9; + const is_64 = arch.ptrBitWidth() == 64; + + const s = path.sep_str; + + if (is_x86) { + if (arch == .x86_64) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "x86_64", nptl })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "x86_64" })); + } + } else if (arch == .i386) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "i386", nptl })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "i386" })); + } + } + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "x86", nptl })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "x86" })); + } + } else if (arch.isARM()) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "arm", nptl })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "arm" })); + } + } else if (arch.isMIPS()) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "mips", nptl })); + } else { + if (is_64) { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" ++ s ++ "mips64" })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" ++ s ++ "mips32" })); + } + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" })); + } + } else if (is_sparc) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc", nptl })); + } else { + if (is_64) { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" ++ s ++ "sparc64" })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" ++ s ++ "sparc32" })); + } + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" })); + } + } else if (is_aarch64) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "aarch64", nptl })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "aarch64" })); + } + } else if (is_ppc) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc", nptl })); + } else { + if (is_64) { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" ++ s ++ "powerpc64" })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" ++ s ++ "powerpc32" })); + } + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" })); + } + } else if (arch.isRISCV()) { + if (opt_nptl) |nptl| { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "riscv", nptl })); + } else { + try args.append("-I"); + try args.append(try path.join(arena, &[_][]const u8{ dir, "riscv" })); + } + } +} + +fn path_from_lib(mod: *Module, arena: *Allocator, sub_path: []const u8) ![]const u8 { + return path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, sub_path }); +} + +const lib_libc = "libc" ++ path.sep_str; +const lib_libc_glibc = lib_libc ++ "glibc" ++ path.sep_str; + +fn lib_path(mod: *Module, arena: *Allocator, sub_path: []const u8) ![]const u8 { + return path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, sub_path }); +} + +fn build_libc_object(mod: *Module, basename: []const u8, c_source_file: Module.CSourceFile) !void { + // TODO: This is extracted into a local variable to work around a stage1 miscompilation. + const emit_bin = Module.EmitLoc{ + .directory = null, // Put it in the cache directory. + .basename = basename, + }; + const sub_module = try Module.create(mod.gpa, .{ + // TODO use the global cache directory here + .zig_cache_directory = mod.zig_cache_directory, + .zig_lib_directory = mod.zig_lib_directory, + .target = mod.getTarget(), + .root_name = mem.split(basename, ".").next().?, + .root_pkg = null, + .output_mode = .Obj, + .rand = mod.rand, + .libc_installation = mod.bin_file.options.libc_installation, + .emit_bin = emit_bin, + .optimize_mode = mod.bin_file.options.optimize_mode, + .want_sanitize_c = false, + .want_stack_check = false, + .want_valgrind = false, + .want_pic = mod.bin_file.options.pic, + .emit_h = null, + .strip = mod.bin_file.options.strip, + .is_native_os = mod.bin_file.options.is_native_os, + .self_exe_path = mod.self_exe_path, + .c_source_files = &[1]Module.CSourceFile{c_source_file}, + .debug_cc = mod.debug_cc, + .debug_link = mod.bin_file.options.debug_link, + }); + defer sub_module.destroy(); + + try sub_module.update(); + + try mod.crt_files.ensureCapacity(mod.gpa, mod.crt_files.count() + 1); + const artifact_path = try std.fs.path.join(mod.gpa, &[_][]const u8{ + sub_module.zig_cache_artifact_directory.path.?, basename, + }); + mod.crt_files.putAssumeCapacityNoClobber(basename, artifact_path); +} diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig index 019da4a39e..3dd040881c 100644 --- a/src-self-hosted/introspect.zig +++ b/src-self-hosted/introspect.zig @@ -1,77 +1,65 @@ -//! Introspection and determination of system libraries needed by zig. - const std = @import("std"); const mem = std.mem; const fs = std.fs; const CacheHash = std.cache_hash.CacheHash; +const Module = @import("Module.zig"); -/// Caller must free result -pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![]u8 { - { - const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib", "zig" }); - errdefer allocator.free(test_zig_dir); +/// Returns the sub_path that worked, or `null` if none did. +/// The path of the returned Directory is relative to `base`. +/// The handle of the returned Directory is open. +fn testZigInstallPrefix(base_dir: fs.Dir) ?Module.Directory { + const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig"; - const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" }); - defer allocator.free(test_index_file); - - if (fs.cwd().openFile(test_index_file, .{})) |file| { - file.close(); - return test_zig_dir; - } else |err| switch (err) { - error.FileNotFound => { - allocator.free(test_zig_dir); - }, - else => |e| return e, - } + zig_dir: { + // Try lib/zig/std/std.zig + const lib_zig = "lib" ++ fs.path.sep_str ++ "zig"; + var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir; + const file = test_zig_dir.openFile(test_index_file, .{}) catch { + test_zig_dir.close(); + break :zig_dir; + }; + file.close(); + return Module.Directory{ .handle = test_zig_dir, .path = lib_zig }; } - // Also try without "zig" - const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib" }); - errdefer allocator.free(test_zig_dir); - - const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" }); - defer allocator.free(test_index_file); - - const file = try fs.cwd().openFile(test_index_file, .{}); + // Try lib/std/std.zig + var test_zig_dir = base_dir.openDir("lib", .{}) catch return null; + const file = test_zig_dir.openFile(test_index_file, .{}) catch { + test_zig_dir.close(); + return null; + }; file.close(); - - return test_zig_dir; + return Module.Directory{ .handle = test_zig_dir, .path = "lib" }; } -/// Caller must free result -pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 { - const self_exe_path = try fs.selfExePathAlloc(allocator); - defer allocator.free(self_exe_path); +/// Both the directory handle and the path are newly allocated resources which the caller now owns. +pub fn findZigLibDir(gpa: *mem.Allocator) !Module.Directory { + const self_exe_path = try fs.selfExePathAlloc(gpa); + defer gpa.free(self_exe_path); + return findZigLibDirFromSelfExe(gpa, self_exe_path); +} + +/// Both the directory handle and the path are newly allocated resources which the caller now owns. +pub fn findZigLibDirFromSelfExe( + allocator: *mem.Allocator, + self_exe_path: []const u8, +) error{ OutOfMemory, FileNotFound }!Module.Directory { + const cwd = fs.cwd(); var cur_path: []const u8 = self_exe_path; - while (true) { - const test_dir = fs.path.dirname(cur_path) orelse "."; + while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { + var base_dir = cwd.openDir(dirname, .{}) catch continue; + defer base_dir.close(); - if (mem.eql(u8, test_dir, cur_path)) { - break; - } - - return testZigInstallPrefix(allocator, test_dir) catch |err| { - cur_path = test_dir; - continue; + const sub_directory = testZigInstallPrefix(base_dir) orelse continue; + return Module.Directory{ + .handle = sub_directory.handle, + .path = try fs.path.join(allocator, &[_][]const u8{ dirname, sub_directory.path.? }), }; } - return error.FileNotFound; } -pub fn resolveZigLibDir(allocator: *mem.Allocator) ![]u8 { - return findZigLibDir(allocator) catch |err| { - std.debug.print( - \\Unable to find zig lib directory: {}. - \\Reinstall Zig or use --zig-install-prefix. - \\ - , .{@errorName(err)}); - - return error.ZigLibDirNotFound; - }; -} - /// Caller owns returned memory. pub fn resolveGlobalCacheDir(allocator: *mem.Allocator) ![]u8 { const appname = "zig"; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 68292fbfe6..626ec6cca8 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -11,11 +11,9 @@ const LibCInstallation = @import("libc_installation.zig").LibCInstallation; pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; pub const Options = struct { - dir: fs.Dir, - /// Redundant with dir. Needed when linking with LLD because we have to pass paths rather - /// than file descriptors. `null` means cwd. OK to pass `null` when `use_lld` is `false`. - dir_path: ?[]const u8, - /// Path to the output file, relative to dir. + /// Where the output will go. + directory: Module.Directory, + /// Path to the output file, relative to `directory`. sub_path: []const u8, target: std.Target, output_mode: std.builtin.OutputMode, @@ -53,6 +51,11 @@ pub const Options = struct { z_defs: bool = false, bind_global_refs_locally: bool, is_native_os: bool, + pic: bool, + valgrind: bool, + stack_check: bool, + single_threaded: bool, + debug_link: bool = false, gc_sections: ?bool = null, allow_shlib_undefined: ?bool = null, linker_script: ?[]const u8 = null, @@ -154,7 +157,7 @@ pub const File = struct { switch (base.tag) { .coff, .elf, .macho => { if (base.file != null) return; - base.file = try base.options.dir.createFile(base.options.sub_path, .{ + base.file = try base.options.directory.handle.createFile(base.options.sub_path, .{ .truncate = false, .read = true, .mode = determineMode(base.options), diff --git a/src-self-hosted/link/C.zig b/src-self-hosted/link/C.zig index 8ec6f91959..277fea7080 100644 --- a/src-self-hosted/link/C.zig +++ b/src-self-hosted/link/C.zig @@ -28,7 +28,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVMHasNoCBackend; if (options.use_lld) return error.LLDHasNoCBackend; - const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) }); + const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) }); errdefer file.close(); var c_file = try allocator.create(C); diff --git a/src-self-hosted/link/Coff.zig b/src-self-hosted/link/Coff.zig index 950e3537d9..6d7e87e898 100644 --- a/src-self-hosted/link/Coff.zig +++ b/src-self-hosted/link/Coff.zig @@ -116,7 +116,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO if (options.use_lld) return error.LLD_LinkingIsTODO_ForCoff; // TODO - const file = try options.dir.createFile(sub_path, .{ + const file = try options.directory.handle.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options), diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 835ab12cfa..d46053493a 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -19,6 +19,7 @@ const File = link.File; const Elf = @This(); const build_options = @import("build_options"); const target_util = @import("../target.zig"); +const fatal = @import("main.zig").fatal; const default_entry_addr = 0x8000000; @@ -222,7 +223,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO - const file = try options.dir.createFile(sub_path, .{ + const file = try options.directory.handle.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options), @@ -844,7 +845,7 @@ fn flushInner(self: *Elf, module: *Module) !void { } // Write the form for the compile unit, which must match the abbrev table above. const name_strp = try self.makeDebugString(self.base.options.root_pkg.?.root_src_path); - const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.?.root_src_dir_path); + const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.?.root_src_directory.path.?); const producer_strp = try self.makeDebugString(link.producer_string); // Currently only one compilation unit is supported, so the address range is simply // identical to the main program header virtual address and memory size. @@ -1199,11 +1200,6 @@ fn flushInner(self: *Elf, module: *Module) !void { } 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; @@ -1292,7 +1288,7 @@ fn linkWithLLD(self: *Elf, module: *Module) !void { try argv.append("-pie"); } - const full_out_path = if (self.base.options.dir_path) |dir_path| + const full_out_path = if (self.base.options.directory.path) |dir_path| try std.fs.path.join(arena, &[_][]const u8{dir_path, self.base.options.sub_path}) else self.base.options.sub_path; @@ -1382,6 +1378,30 @@ fn linkWithLLD(self: *Elf, module: *Module) !void { // Positional arguments to the linker such as object files. try argv.appendSlice(self.base.options.objects); + for (module.c_object_table.items()) |entry| { + const c_object = entry.key; + switch (c_object.status) { + .new => unreachable, + .failure => return error.NotAllCSourceFilesAvailableToLink, + .success => |full_obj_path| { + try argv.append(full_obj_path); + }, + } + } + + // 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); + + const obj_basename = self.base.intermediary_basename.?; + const full_obj_path = if (self.base.options.directory.path) |dir_path| + try std.fs.path.join(arena, &[_][]const u8{dir_path, obj_basename}) + else + obj_basename; + try argv.append(full_obj_path); + } + // TODO compiler-rt and libc //if (!g->is_dummy_so && (g->out_type == OutTypeExe || is_dyn_lib)) { // if (g->libc_link_lib == nullptr) { @@ -1461,10 +1481,31 @@ fn linkWithLLD(self: *Elf, module: *Module) !void { try argv.append("-Bsymbolic"); } - for (argv.items) |arg| { - std.debug.print("{} ", .{arg}); + if (self.base.options.debug_link) { + for (argv.items[0 .. argv.items.len - 1]) |arg| { + std.debug.print("{} ", .{arg}); + } + std.debug.print("{}\n", .{argv.items[argv.items.len - 1]}); } - @panic("invoke LLD"); + + // Oh, snapplesauce! We need null terminated argv. + // TODO allocSentinel crashed stage1 so this is working around it. + const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1); + new_argv_with_sentinel[argv.items.len] = null; + const new_argv = new_argv_with_sentinel[0..argv.items.len: null]; + for (argv.items) |arg, i| { + new_argv[i] = try arena.dupeZ(u8, arg); + } + + const ZigLLDLink = @import("../llvm.zig").ZigLLDLink; + const ok = ZigLLDLink(.ELF, new_argv.ptr, new_argv.len, append_diagnostic, 0, 0); + if (!ok) return error.LLDReportedFailure; +} + +fn append_diagnostic(context: usize, ptr: [*]const u8, len: usize) callconv(.C) void { + // TODO collect diagnostics and handle cleanly + const msg = ptr[0..len]; + std.log.err("LLD: {}", .{msg}); } fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void { @@ -2681,7 +2722,7 @@ fn dbgLineNeededHeaderBytes(self: Elf) u32 { directory_count * 8 + file_name_count * 8 + // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. - self.base.options.root_pkg.?.root_src_dir_path.len + + self.base.options.root_pkg.?.root_src_directory.path.?.len + self.base.options.root_pkg.?.root_src_path.len); } diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index 3ffa606d04..b57f3d303a 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -140,7 +140,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO - const file = try options.dir.createFile(sub_path, .{ + const file = try options.directory.handle.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options), diff --git a/src-self-hosted/link/Wasm.zig b/src-self-hosted/link/Wasm.zig index 46bafaaf10..bea36723ff 100644 --- a/src-self-hosted/link/Wasm.zig +++ b/src-self-hosted/link/Wasm.zig @@ -56,7 +56,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO // TODO: read the file and keep vaild parts instead of truncating - const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true }); + const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); errdefer file.close(); const wasm = try allocator.create(Wasm); diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index 4159968252..d65e046169 100644 --- a/src-self-hosted/llvm.zig +++ b/src-self-hosted/llvm.zig @@ -1,293 +1,20 @@ -const c = @import("c.zig"); -const assert = @import("std").debug.assert; +//! We do this instead of @cImport because the self-hosted compiler is easier +//! to bootstrap if it does not depend on translate-c. -// we wrap the c module for 3 reasons: -// 1. to avoid accidentally calling the non-thread-safe functions -// 2. patch up some of the types to remove nullability -// 3. some functions have been augmented by zig_llvm.cpp to be more powerful, -// such as ZigLLVMTargetMachineEmitToFile - -pub const AttributeIndex = c_uint; -pub const Bool = c_int; - -pub const Builder = c.LLVMBuilderRef.Child.Child; -pub const Context = c.LLVMContextRef.Child.Child; -pub const Module = c.LLVMModuleRef.Child.Child; -pub const Value = c.LLVMValueRef.Child.Child; -pub const Type = c.LLVMTypeRef.Child.Child; -pub const BasicBlock = c.LLVMBasicBlockRef.Child.Child; -pub const Attribute = c.LLVMAttributeRef.Child.Child; -pub const Target = c.LLVMTargetRef.Child.Child; -pub const TargetMachine = c.LLVMTargetMachineRef.Child.Child; -pub const TargetData = c.LLVMTargetDataRef.Child.Child; -pub const DIBuilder = c.ZigLLVMDIBuilder; -pub const DIFile = c.ZigLLVMDIFile; -pub const DICompileUnit = c.ZigLLVMDICompileUnit; - -pub const ABIAlignmentOfType = c.LLVMABIAlignmentOfType; -pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex; -pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag; -pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag; -pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; -pub const ConstAllOnes = c.LLVMConstAllOnes; -pub const ConstArray = c.LLVMConstArray; -pub const ConstBitCast = c.LLVMConstBitCast; -pub const ConstIntOfArbitraryPrecision = c.LLVMConstIntOfArbitraryPrecision; -pub const ConstNeg = c.LLVMConstNeg; -pub const ConstStructInContext = c.LLVMConstStructInContext; -pub const DIBuilderFinalize = c.ZigLLVMDIBuilderFinalize; -pub const DisposeBuilder = c.LLVMDisposeBuilder; -pub const DisposeDIBuilder = c.ZigLLVMDisposeDIBuilder; -pub const DisposeMessage = c.LLVMDisposeMessage; -pub const DisposeModule = c.LLVMDisposeModule; -pub const DisposeTargetData = c.LLVMDisposeTargetData; -pub const DisposeTargetMachine = c.LLVMDisposeTargetMachine; -pub const DoubleTypeInContext = c.LLVMDoubleTypeInContext; -pub const DumpModule = c.LLVMDumpModule; -pub const FP128TypeInContext = c.LLVMFP128TypeInContext; -pub const FloatTypeInContext = c.LLVMFloatTypeInContext; -pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName; -pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext; -pub const GetUndef = c.LLVMGetUndef; -pub const HalfTypeInContext = c.LLVMHalfTypeInContext; -pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers; -pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters; -pub const InitializeAllTargetInfos = c.LLVMInitializeAllTargetInfos; -pub const InitializeAllTargetMCs = c.LLVMInitializeAllTargetMCs; -pub const InitializeAllTargets = c.LLVMInitializeAllTargets; -pub const InsertBasicBlockInContext = c.LLVMInsertBasicBlockInContext; -pub const Int128TypeInContext = c.LLVMInt128TypeInContext; -pub const Int16TypeInContext = c.LLVMInt16TypeInContext; -pub const Int1TypeInContext = c.LLVMInt1TypeInContext; -pub const Int32TypeInContext = c.LLVMInt32TypeInContext; -pub const Int64TypeInContext = c.LLVMInt64TypeInContext; -pub const Int8TypeInContext = c.LLVMInt8TypeInContext; -pub const IntPtrTypeForASInContext = c.LLVMIntPtrTypeForASInContext; -pub const IntPtrTypeInContext = c.LLVMIntPtrTypeInContext; -pub const LabelTypeInContext = c.LLVMLabelTypeInContext; -pub const MDNodeInContext = c.LLVMMDNodeInContext; -pub const MDStringInContext = c.LLVMMDStringInContext; -pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext; -pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext; -pub const SetAlignment = c.LLVMSetAlignment; -pub const SetDataLayout = c.LLVMSetDataLayout; -pub const SetGlobalConstant = c.LLVMSetGlobalConstant; -pub const SetInitializer = c.LLVMSetInitializer; -pub const SetLinkage = c.LLVMSetLinkage; -pub const SetTarget = c.LLVMSetTarget; -pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr; -pub const SetVolatile = c.LLVMSetVolatile; -pub const StructTypeInContext = c.LLVMStructTypeInContext; -pub const TokenTypeInContext = c.LLVMTokenTypeInContext; -pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; -pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; - -pub const AddGlobal = LLVMAddGlobal; -extern fn LLVMAddGlobal(M: *Module, Ty: *Type, Name: [*:0]const u8) ?*Value; - -pub const ConstStringInContext = LLVMConstStringInContext; -extern fn LLVMConstStringInContext(C: *Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) ?*Value; - -pub const ConstInt = LLVMConstInt; -extern fn LLVMConstInt(IntTy: *Type, N: c_ulonglong, SignExtend: Bool) ?*Value; - -pub const BuildLoad = LLVMBuildLoad; -extern fn LLVMBuildLoad(arg0: *Builder, PointerVal: *Value, Name: [*:0]const u8) ?*Value; - -pub const ConstNull = LLVMConstNull; -extern fn LLVMConstNull(Ty: *Type) ?*Value; - -pub const CreateStringAttribute = LLVMCreateStringAttribute; -extern fn LLVMCreateStringAttribute( - C: *Context, - K: [*]const u8, - KLength: c_uint, - V: [*]const u8, - VLength: c_uint, -) ?*Attribute; - -pub const CreateEnumAttribute = LLVMCreateEnumAttribute; -extern fn LLVMCreateEnumAttribute(C: *Context, KindID: c_uint, Val: u64) ?*Attribute; - -pub const AddFunction = LLVMAddFunction; -extern fn LLVMAddFunction(M: *Module, Name: [*:0]const u8, FunctionTy: *Type) ?*Value; - -pub const CreateCompileUnit = ZigLLVMCreateCompileUnit; -extern fn ZigLLVMCreateCompileUnit( - dibuilder: *DIBuilder, - lang: c_uint, - difile: *DIFile, - producer: [*:0]const u8, - is_optimized: bool, - flags: [*:0]const u8, - runtime_version: c_uint, - split_name: [*:0]const u8, - dwo_id: u64, - emit_debug_info: bool, -) ?*DICompileUnit; - -pub const CreateFile = ZigLLVMCreateFile; -extern fn ZigLLVMCreateFile(dibuilder: *DIBuilder, filename: [*:0]const u8, directory: [*:0]const u8) ?*DIFile; - -pub const ArrayType = LLVMArrayType; -extern fn LLVMArrayType(ElementType: *Type, ElementCount: c_uint) ?*Type; - -pub const CreateDIBuilder = ZigLLVMCreateDIBuilder; -extern fn ZigLLVMCreateDIBuilder(module: *Module, allow_unresolved: bool) ?*DIBuilder; - -pub const PointerType = LLVMPointerType; -extern fn LLVMPointerType(ElementType: *Type, AddressSpace: c_uint) ?*Type; - -pub const CreateBuilderInContext = LLVMCreateBuilderInContext; -extern fn LLVMCreateBuilderInContext(C: *Context) ?*Builder; - -pub const IntTypeInContext = LLVMIntTypeInContext; -extern fn LLVMIntTypeInContext(C: *Context, NumBits: c_uint) ?*Type; - -pub const ModuleCreateWithNameInContext = LLVMModuleCreateWithNameInContext; -extern fn LLVMModuleCreateWithNameInContext(ModuleID: [*:0]const u8, C: *Context) ?*Module; - -pub const VoidTypeInContext = LLVMVoidTypeInContext; -extern fn LLVMVoidTypeInContext(C: *Context) ?*Type; - -pub const ContextCreate = LLVMContextCreate; -extern fn LLVMContextCreate() ?*Context; - -pub const ContextDispose = LLVMContextDispose; -extern fn LLVMContextDispose(C: *Context) void; - -pub const CopyStringRepOfTargetData = LLVMCopyStringRepOfTargetData; -extern fn LLVMCopyStringRepOfTargetData(TD: *TargetData) ?[*:0]u8; - -pub const CreateTargetDataLayout = LLVMCreateTargetDataLayout; -extern fn LLVMCreateTargetDataLayout(T: *TargetMachine) ?*TargetData; - -pub const CreateTargetMachine = ZigLLVMCreateTargetMachine; -extern fn ZigLLVMCreateTargetMachine( - T: *Target, - Triple: [*:0]const u8, - CPU: [*:0]const u8, - Features: [*:0]const u8, - Level: CodeGenOptLevel, - Reloc: RelocMode, - CodeModel: CodeModel, - function_sections: bool, -) ?*TargetMachine; - -pub const GetHostCPUName = LLVMGetHostCPUName; -extern fn LLVMGetHostCPUName() ?[*:0]u8; - -pub const GetNativeFeatures = ZigLLVMGetNativeFeatures; -extern fn ZigLLVMGetNativeFeatures() ?[*:0]u8; - -pub const GetElementType = LLVMGetElementType; -extern fn LLVMGetElementType(Ty: *Type) *Type; - -pub const TypeOf = LLVMTypeOf; -extern fn LLVMTypeOf(Val: *Value) *Type; - -pub const BuildStore = LLVMBuildStore; -extern fn LLVMBuildStore(arg0: *Builder, Val: *Value, Ptr: *Value) ?*Value; - -pub const BuildAlloca = LLVMBuildAlloca; -extern fn LLVMBuildAlloca(arg0: *Builder, Ty: *Type, Name: ?[*:0]const u8) ?*Value; - -pub const ConstInBoundsGEP = LLVMConstInBoundsGEP; -pub extern fn LLVMConstInBoundsGEP(ConstantVal: *Value, ConstantIndices: [*]*Value, NumIndices: c_uint) ?*Value; - -pub const GetTargetFromTriple = LLVMGetTargetFromTriple; -extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **Target, ErrorMessage: ?*[*:0]u8) Bool; - -pub const VerifyModule = LLVMVerifyModule; -extern fn LLVMVerifyModule(M: *Module, Action: VerifierFailureAction, OutMessage: *?[*:0]u8) Bool; - -pub const GetInsertBlock = LLVMGetInsertBlock; -extern fn LLVMGetInsertBlock(Builder: *Builder) *BasicBlock; - -pub const FunctionType = LLVMFunctionType; -extern fn LLVMFunctionType( - ReturnType: *Type, - ParamTypes: [*]*Type, - ParamCount: c_uint, - IsVarArg: Bool, -) ?*Type; - -pub const GetParam = LLVMGetParam; -extern fn LLVMGetParam(Fn: *Value, Index: c_uint) *Value; - -pub const AppendBasicBlockInContext = LLVMAppendBasicBlockInContext; -extern fn LLVMAppendBasicBlockInContext(C: *Context, Fn: *Value, Name: [*:0]const u8) ?*BasicBlock; - -pub const PositionBuilderAtEnd = LLVMPositionBuilderAtEnd; -extern fn LLVMPositionBuilderAtEnd(Builder: *Builder, Block: *BasicBlock) void; - -pub const AbortProcessAction = VerifierFailureAction.LLVMAbortProcessAction; -pub const PrintMessageAction = VerifierFailureAction.LLVMPrintMessageAction; -pub const ReturnStatusAction = VerifierFailureAction.LLVMReturnStatusAction; -pub const VerifierFailureAction = c.LLVMVerifierFailureAction; - -pub const CodeGenLevelNone = CodeGenOptLevel.LLVMCodeGenLevelNone; -pub const CodeGenLevelLess = CodeGenOptLevel.LLVMCodeGenLevelLess; -pub const CodeGenLevelDefault = CodeGenOptLevel.LLVMCodeGenLevelDefault; -pub const CodeGenLevelAggressive = CodeGenOptLevel.LLVMCodeGenLevelAggressive; -pub const CodeGenOptLevel = c.LLVMCodeGenOptLevel; - -pub const RelocDefault = RelocMode.LLVMRelocDefault; -pub const RelocStatic = RelocMode.LLVMRelocStatic; -pub const RelocPIC = RelocMode.LLVMRelocPIC; -pub const RelocDynamicNoPic = RelocMode.LLVMRelocDynamicNoPic; -pub const RelocMode = c.LLVMRelocMode; - -pub const CodeModelDefault = CodeModel.LLVMCodeModelDefault; -pub const CodeModelJITDefault = CodeModel.LLVMCodeModelJITDefault; -pub const CodeModelSmall = CodeModel.LLVMCodeModelSmall; -pub const CodeModelKernel = CodeModel.LLVMCodeModelKernel; -pub const CodeModelMedium = CodeModel.LLVMCodeModelMedium; -pub const CodeModelLarge = CodeModel.LLVMCodeModelLarge; -pub const CodeModel = c.LLVMCodeModel; - -pub const EmitAssembly = EmitOutputType.ZigLLVM_EmitAssembly; -pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary; -pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr; -pub const EmitOutputType = c.ZigLLVM_EmitOutputType; - -pub const CCallConv = CallConv.LLVMCCallConv; -pub const FastCallConv = CallConv.LLVMFastCallConv; -pub const ColdCallConv = CallConv.LLVMColdCallConv; -pub const WebKitJSCallConv = CallConv.LLVMWebKitJSCallConv; -pub const AnyRegCallConv = CallConv.LLVMAnyRegCallConv; -pub const X86StdcallCallConv = CallConv.LLVMX86StdcallCallConv; -pub const X86FastcallCallConv = CallConv.LLVMX86FastcallCallConv; -pub const CallConv = c.LLVMCallConv; - -pub const CallAttr = extern enum { - Auto, - NeverTail, - NeverInline, - AlwaysTail, - AlwaysInline, -}; - -fn removeNullability(comptime T: type) type { - comptime assert(@typeInfo(T).Pointer.size == .C); - return *T.Child; -} - -pub const BuildRet = LLVMBuildRet; -extern fn LLVMBuildRet(arg0: *Builder, V: ?*Value) ?*Value; - -pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile; -extern fn ZigLLVMTargetMachineEmitToFile( - targ_machine_ref: *TargetMachine, - module_ref: *Module, - filename: [*:0]const u8, - output_type: EmitOutputType, - error_message: *[*:0]u8, - is_debug: bool, - is_small: bool, +pub extern fn ZigLLDLink( + oformat: ZigLLVM_ObjectFormatType, + args: [*:null]const ?[*:0]const u8, + arg_count: usize, + append_diagnostic: fn (context: usize, ptr: [*]const u8, len: usize) callconv(.C) void, + context_stdout: usize, + context_stderr: usize, ) bool; -pub const BuildCall = ZigLLVMBuildCall; -extern fn ZigLLVMBuildCall(B: *Builder, Fn: *Value, Args: [*]*Value, NumArgs: c_uint, CC: CallConv, fn_inline: CallAttr, Name: [*:0]const u8) ?*Value; - -pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage; +pub const ZigLLVM_ObjectFormatType = extern enum(c_int) { + Unknown, + COFF, + ELF, + MachO, + Wasm, + XCOFF, +}; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index bc3e5a8e6c..154e09bc68 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -191,7 +191,14 @@ const usage_build_generic = \\ ReleaseSmall Optimize for small binary, safety off \\ -fPIC Force-enable Position Independent Code \\ -fno-PIC Force-disable Position Independent Code + \\ -fstack-check Enable stack probing in unsafe builds + \\ -fno-stack-check Disable stack probing in safe builds + \\ -fsanitize-c Enable C undefined behavior detection in unsafe builds + \\ -fno-sanitize-c Disable C undefined behavior detection in safe builds + \\ -fvalgrind Include valgrind client requests in release builds + \\ -fno-valgrind Omit valgrind client requests in debug builds \\ --strip Exclude debug symbols + \\ --single-threaded Code assumes it is only used single-threaded \\ -ofmt=[mode] Override target object format \\ elf Executable and Linking Format \\ c Compile to C source code @@ -262,6 +269,7 @@ pub fn buildOutputType( var root_src_file: ?[]const u8 = null; var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 }; var strip = false; + var single_threaded = false; var watch = false; var debug_tokenize = false; var debug_ast_tree = false; @@ -287,6 +295,8 @@ pub fn buildOutputType( var enable_cache: ?bool = null; var want_pic: ?bool = null; var want_sanitize_c: ?bool = null; + var want_stack_check: ?bool = null; + var want_valgrind: ?bool = null; var rdynamic: bool = false; var only_pp_or_asm = false; var linker_script: ?[]const u8 = null; @@ -320,7 +330,7 @@ pub fn buildOutputType( var rpath_list = std.ArrayList([]const u8).init(gpa); defer rpath_list.deinit(); - var c_source_files = std.ArrayList([]const u8).init(gpa); + var c_source_files = std.ArrayList(Module.CSourceFile).init(gpa); defer c_source_files.deinit(); var link_objects = std.ArrayList([]const u8).init(gpa); @@ -463,6 +473,18 @@ pub fn buildOutputType( want_pic = true; } else if (mem.eql(u8, arg, "-fno-PIC")) { want_pic = false; + } else if (mem.eql(u8, arg, "-fstack-check")) { + want_stack_check = true; + } else if (mem.eql(u8, arg, "-fno-stack-check")) { + want_stack_check = false; + } else if (mem.eql(u8, arg, "-fsanitize-c")) { + want_sanitize_c = true; + } else if (mem.eql(u8, arg, "-fno-sanitize-c")) { + want_sanitize_c = false; + } else if (mem.eql(u8, arg, "-fvalgrind")) { + want_valgrind = true; + } else if (mem.eql(u8, arg, "-fno-valgrind")) { + want_valgrind = false; } else if (mem.eql(u8, arg, "-fLLVM")) { use_llvm = true; } else if (mem.eql(u8, arg, "-fno-LLVM")) { @@ -501,6 +523,8 @@ pub fn buildOutputType( link_mode = .Static; } else if (mem.eql(u8, arg, "--strip")) { strip = true; + } else if (mem.eql(u8, arg, "--single-threaded")) { + single_threaded = true; } else if (mem.eql(u8, arg, "--eh-frame-hdr")) { link_eh_frame_hdr = true; } else if (mem.eql(u8, arg, "-Bsymbolic")) { @@ -541,7 +565,8 @@ pub fn buildOutputType( { try link_objects.append(arg); } else if (Module.hasAsmExt(arg) or Module.hasCExt(arg) or Module.hasCppExt(arg)) { - try c_source_files.append(arg); + // TODO a way to pass extra flags on the CLI + try c_source_files.append(.{ .src_path = arg }); } else if (mem.endsWith(u8, arg, ".so") or mem.endsWith(u8, arg, ".dylib") or mem.endsWith(u8, arg, ".dll")) @@ -586,7 +611,7 @@ pub fn buildOutputType( .positional => { 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), + .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(.{ .src_path = it.only_arg }), .unknown, .so => try link_objects.append(it.only_arg), } }, @@ -812,7 +837,7 @@ pub fn buildOutputType( // .yes => |p| p, // else => c_source_file.source_path, // }; - // const basename = std.fs.path.basename(src_path); + // const basename = fs.path.basename(src_path); // c_source_file.preprocessor_only_basename = basename; //} //emit_bin = .no; @@ -839,7 +864,7 @@ pub fn buildOutputType( const basename = fs.path.basename(file); break :blk mem.split(basename, ".").next().?; } else if (c_source_files.items.len == 1) { - const basename = fs.path.basename(c_source_files.items[0]); + const basename = fs.path.basename(c_source_files.items[0].src_path); break :blk mem.split(basename, ".").next().?; } else if (link_objects.items.len == 1) { const basename = fs.path.basename(link_objects.items[0]); @@ -966,19 +991,71 @@ pub fn buildOutputType( } }; - const bin_path = switch (emit_bin) { - .no => { - fatal("-fno-emit-bin not supported yet", .{}); + var cleanup_emit_bin_dir: ?fs.Dir = null; + defer if (cleanup_emit_bin_dir) |*dir| dir.close(); + + const emit_bin_loc: ?Module.EmitLoc = switch (emit_bin) { + .no => null, + .yes_default_path => Module.EmitLoc{ + .directory = .{ .path = null, .handle = fs.cwd() }, + .basename = try std.zig.binNameAlloc( + arena, + root_name, + target_info.target, + output_mode, + link_mode, + object_format, + ), + }, + .yes => |full_path| b: { + const basename = fs.path.basename(full_path); + if (fs.path.dirname(full_path)) |dirname| { + const handle = try fs.cwd().openDir(dirname, .{}); + cleanup_emit_bin_dir = handle; + break :b Module.EmitLoc{ + .basename = basename, + .directory = .{ + .path = dirname, + .handle = handle, + }, + }; + } else { + break :b Module.EmitLoc{ + .basename = basename, + .directory = .{ .path = null, .handle = fs.cwd() }, + }; + } + }, + }; + + var cleanup_emit_h_dir: ?fs.Dir = null; + defer if (cleanup_emit_h_dir) |*dir| dir.close(); + + const emit_h_loc: ?Module.EmitLoc = switch (emit_h) { + .no => null, + .yes_default_path => Module.EmitLoc{ + .directory = .{ .path = null, .handle = fs.cwd() }, + .basename = try std.fmt.allocPrint(arena, "{}.h", .{root_name}), + }, + .yes => |full_path| b: { + const basename = fs.path.basename(full_path); + if (fs.path.dirname(full_path)) |dirname| { + const handle = try fs.cwd().openDir(dirname, .{}); + cleanup_emit_h_dir = handle; + break :b Module.EmitLoc{ + .basename = basename, + .directory = .{ + .path = dirname, + .handle = handle, + }, + }; + } else { + break :b Module.EmitLoc{ + .basename = basename, + .directory = .{ .path = null, .handle = fs.cwd() }, + }; + } }, - .yes_default_path => try std.zig.binNameAlloc( - arena, - root_name, - target_info.target, - output_mode, - link_mode, - object_format, - ), - .yes => |p| p, }; const zir_out_path: ?[]const u8 = switch (emit_zir) { @@ -995,19 +1072,13 @@ pub fn buildOutputType( }; const root_pkg = if (root_src_file) |src_path| try Package.create(gpa, fs.cwd(), ".", src_path) else null; - defer if (root_pkg) |pkg| pkg.destroy(); - - const emit_h_path: ?[]const u8 = switch (emit_h) { - .yes => |p| p, - .no => null, - .yes_default_path => try std.fmt.allocPrint(arena, "{}.h", .{root_name}), - }; + defer if (root_pkg) |pkg| pkg.destroy(gpa); const self_exe_path = try fs.selfExePathAlloc(arena); - const zig_lib_dir = introspect.resolveZigLibDir(gpa) catch |err| { + var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); }; - defer gpa.free(zig_lib_dir); + defer zig_lib_directory.handle.close(); const random_seed = blk: { var random_seed: u64 = undefined; @@ -1025,17 +1096,32 @@ pub fn buildOutputType( }; } + const cache_parent_dir = if (root_pkg) |pkg| pkg.root_src_directory.handle else fs.cwd(); + var cache_dir = try cache_parent_dir.makeOpenPath("zig-cache", .{}); + defer cache_dir.close(); + const zig_cache_directory: Module.Directory = .{ + .handle = cache_dir, + .path = blk: { + if (root_pkg) |pkg| { + if (pkg.root_src_directory.path) |p| { + break :blk try fs.path.join(arena, &[_][]const u8{ p, "zig-cache" }); + } + } + break :blk "zig-cache"; + }, + }; + const module = Module.create(gpa, .{ - .zig_lib_dir = zig_lib_dir, + .zig_lib_directory = zig_lib_directory, + .zig_cache_directory = zig_cache_directory, .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, - .bin_file_dir = fs.cwd(), - .bin_file_path = bin_path, + .emit_bin = emit_bin_loc, + .emit_h = emit_h_loc, .link_mode = link_mode, .object_format = object_format, .optimize_mode = build_mode, @@ -1049,11 +1135,12 @@ pub fn buildOutputType( .framework_dirs = framework_dirs.items, .frameworks = frameworks.items, .system_libs = system_libs.items, - .emit_h = emit_h_path, .link_libc = link_libc, .link_libcpp = link_libcpp, .want_pic = want_pic, .want_sanitize_c = want_sanitize_c, + .want_stack_check = want_stack_check, + .want_valgrind = want_valgrind, .use_llvm = use_llvm, .use_lld = use_lld, .use_clang = use_clang, @@ -1070,11 +1157,14 @@ pub fn buildOutputType( .link_eh_frame_hdr = link_eh_frame_hdr, .stack_size_override = stack_size_override, .strip = strip, + .single_threaded = single_threaded, .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, + .debug_cc = debug_cc, + .debug_link = debug_link, }) catch |err| { fatal("unable to create module: {}", .{@errorName(err)}); }; @@ -1121,9 +1211,7 @@ pub fn buildOutputType( } fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void { - var timer = try std.time.Timer.start(); try module.update(); - const update_nanos = timer.read(); var errors = try module.getAllErrorsAlloc(); defer errors.deinit(module.gpa); diff --git a/src-self-hosted/print_env.zig b/src-self-hosted/print_env.zig index 907e9e234d..d1956911e9 100644 --- a/src-self-hosted/print_env.zig +++ b/src-self-hosted/print_env.zig @@ -2,15 +2,19 @@ const std = @import("std"); const build_options = @import("build_options"); const introspect = @import("introspect.zig"); const Allocator = std.mem.Allocator; +const fatal = @import("main.zig").fatal; pub fn cmdEnv(gpa: *Allocator, args: []const []const u8, stdout: anytype) !void { - const zig_lib_dir = introspect.resolveZigLibDir(gpa) catch |err| { - std.debug.print("unable to find zig installation directory: {}\n", .{@errorName(err)}); - std.process.exit(1); - }; - defer gpa.free(zig_lib_dir); + const self_exe_path = try std.fs.selfExePathAlloc(gpa); + defer gpa.free(self_exe_path); - const zig_std_dir = try std.fs.path.join(gpa, &[_][]const u8{ zig_lib_dir, "std" }); + var zig_lib_directory = introspect.findZigLibDirFromSelfExe(gpa, self_exe_path) catch |err| { + fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); + }; + defer gpa.free(zig_lib_directory.path.?); + defer zig_lib_directory.handle.close(); + + const zig_std_dir = try std.fs.path.join(gpa, &[_][]const u8{ zig_lib_directory.path.?, "std" }); defer gpa.free(zig_std_dir); const global_cache_dir = try introspect.resolveGlobalCacheDir(gpa); @@ -22,8 +26,11 @@ pub fn cmdEnv(gpa: *Allocator, args: []const []const u8, stdout: anytype) !void var jws = std.json.WriteStream(@TypeOf(bos_stream), 6).init(bos_stream); try jws.beginObject(); + try jws.objectField("zig_exe"); + try jws.emitString(self_exe_path); + try jws.objectField("lib_dir"); - try jws.emitString(zig_lib_dir); + try jws.emitString(zig_lib_directory.path.?); try jws.objectField("std_dir"); try jws.emitString(zig_std_dir); diff --git a/src-self-hosted/print_targets.zig b/src-self-hosted/print_targets.zig index e6ed33b80c..724cb7a9ac 100644 --- a/src-self-hosted/print_targets.zig +++ b/src-self-hosted/print_targets.zig @@ -17,16 +17,14 @@ pub fn cmdTargets( stdout: anytype, native_target: Target, ) !void { - const zig_lib_dir_path = introspect.resolveZigLibDir(allocator) catch |err| { + var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| { fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); }; - defer allocator.free(zig_lib_dir_path); + defer zig_lib_directory.handle.close(); + defer allocator.free(zig_lib_directory.path.?); - var zig_lib_dir = try fs.cwd().openDir(zig_lib_dir_path, .{}); - defer zig_lib_dir.close(); - - const glibc_abi = try glibc.loadMetaData(allocator, zig_lib_dir); - errdefer glibc_abi.destroy(allocator); + const glibc_abi = try glibc.loadMetaData(allocator, zig_lib_directory.handle); + defer glibc_abi.destroy(allocator); var bos = io.bufferedOutStream(stdout); const bos_stream = bos.outStream(); diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig index 127abeeadc..b09c363d8a 100644 --- a/src-self-hosted/target.zig +++ b/src-self-hosted/target.zig @@ -117,10 +117,10 @@ pub fn cannotDynamicLink(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. 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, @@ -131,6 +131,40 @@ pub fn requiresPIE(target: std.Target) bool { return target.isAndroid(); } +/// This function returns whether non-pic code is completely invalid on the given target. +pub fn requiresPIC(target: std.Target, linking_libc: bool) bool { + return target.isAndroid() or + target.os.tag == .windows or target.os.tag == .uefi or + osRequiresLibC(target) or + (linking_libc and target.isGnuLibC()); +} + +/// This is not whether the target supports Position Independent Code, but whether the -fPIC +/// C compiler argument is valid to Clang. +pub fn supports_fpic(target: std.Target) bool { + return target.os.tag != .windows; +} + pub fn libc_needs_crti_crtn(target: std.Target) bool { return !(target.cpu.arch.isRISCV() or target.isAndroid()); } + +pub fn isSingleThreaded(target: std.Target) bool { + return target.isWasm(); +} + +/// Valgrind supports more, but Zig does not support them yet. +pub fn hasValgrindSupport(target: std.Target) bool { + switch (target.cpu.arch) { + .x86_64 => { + return target.os.tag == .linux or target.isDarwin() or target.os.tag == .solaris or + (target.os.tag == .windows and target.abi != .msvc); + }, + else => return false, + } +} + +pub fn supportsStackProbing(target: std.Target) bool { + return target.os.tag != .windows and target.os.tag != .uefi and + (target.cpu.arch == .i386 or target.cpu.arch == .x86_64); +} diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index f23695257b..3fd6aa3bab 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -407,8 +407,9 @@ pub const TestContext = struct { const root_node = try progress.start("tests", self.cases.items.len); defer root_node.end(); - const zig_lib_dir = try introspect.resolveZigLibDir(std.testing.allocator); - defer std.testing.allocator.free(zig_lib_dir); + var zig_lib_directory = try introspect.findZigLibDir(std.testing.allocator); + defer zig_lib_directory.handle.close(); + defer std.testing.allocator.free(zig_lib_directory.path.?); const random_seed = blk: { var random_seed: u64 = undefined; @@ -427,7 +428,7 @@ pub const TestContext = struct { progress.initial_delay_ns = 0; progress.refresh_rate_ns = 0; - try self.runOneCase(std.testing.allocator, &prg_node, case, zig_lib_dir, &default_prng.random); + try self.runOneCase(std.testing.allocator, &prg_node, case, zig_lib_directory, &default_prng.random); } } @@ -436,7 +437,7 @@ pub const TestContext = struct { allocator: *Allocator, root_node: *std.Progress.Node, case: Case, - zig_lib_dir: []const u8, + zig_lib_directory: Module.Directory, rand: *std.rand.Random, ) !void { const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target); @@ -449,15 +450,32 @@ pub const TestContext = struct { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); + var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); + defer cache_dir.close(); + const bogus_path = "bogus"; // TODO this will need to be fixed before we can test LLVM extensions + const zig_cache_directory: Module.Directory = .{ + .handle = cache_dir, + .path = try std.fs.path.join(arena, &[_][]const u8{ bogus_path, "zig-cache" }), + }; + const tmp_src_path = if (case.extension == .Zig) "test_case.zig" else if (case.extension == .ZIR) "test_case.zir" else unreachable; const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); - defer root_pkg.destroy(); + defer root_pkg.destroy(allocator); const ofmt: ?std.builtin.ObjectFormat = if (case.cbe) .c else null; const bin_name = try std.zig.binNameAlloc(arena, "test_case", target, case.output_mode, null, ofmt); + const emit_directory: Module.Directory = .{ + .path = bogus_path, + .handle = tmp.dir, + }; + const emit_bin: Module.EmitLoc = .{ + .directory = emit_directory, + .basename = bin_name, + }; const module = try Module.create(allocator, .{ - .zig_lib_dir = zig_lib_dir, + .zig_cache_directory = zig_cache_directory, + .zig_lib_directory = zig_lib_directory, .rand = rand, .root_name = "test_case", .target = target, @@ -467,8 +485,7 @@ pub const TestContext = struct { .output_mode = case.output_mode, // TODO: support testing optimizations .optimize_mode = .Debug, - .bin_file_dir = tmp.dir, - .bin_file_path = bin_name, + .emit_bin = emit_bin, .root_pkg = root_pkg, .keep_source_files_loaded = true, .object_format = ofmt,