diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Compilation.zig | 84 | ||||
| -rw-r--r-- | src/link.zig | 1 | ||||
| -rw-r--r-- | src/link/Elf.zig | 172 | ||||
| -rw-r--r-- | src/main.zig | 1 |
4 files changed, 144 insertions, 114 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index a522a4173c..c28b27b588 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3,10 +3,11 @@ const Compilation = @This(); const std = @import("std"); const mem = std.mem; const Allocator = std.mem.Allocator; -const Value = @import("value.zig").Value; const assert = std.debug.assert; const log = std.log.scoped(.compilation); const Target = std.Target; + +const Value = @import("value.zig").Value; const target_util = @import("target.zig"); const Package = @import("Package.zig"); const link = @import("link.zig"); @@ -286,6 +287,13 @@ pub const InitOptions = struct { emit_h: ?EmitLoc = null, link_mode: ?std.builtin.LinkMode = null, dll_export_fns: ?bool = false, + /// Normally when using LLD to link, Zig uses a file named "lld.id" in the + /// same directory as the output binary which contains the hash of the link + /// operation, allowing Zig to skip linking when the hash would be unchanged. + /// In the case that the output binary is being emitted into a directory which + /// is externally modified - essentially anything other than zig-cache - then + /// this flag would be set to disable this machinery to avoid false positives. + disable_lld_caching: bool = false, object_format: ?std.builtin.ObjectFormat = null, optimize_mode: std.builtin.Mode = .Debug, keep_source_files_loaded: bool = false, @@ -371,6 +379,26 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const ofmt = options.object_format orelse options.target.getObjectFormat(); + // Make a decision on whether to use LLVM or our own backend. + const use_llvm = if (options.use_llvm) |explicit| explicit else blk: { + // If we have no zig code to compile, no need for LLVM. + if (options.root_pkg == null) + break :blk false; + + // If we are the stage1 compiler, we depend on the stage1 c++ llvm backend + // to compile zig code. + if (build_options.is_stage1) + break :blk true; + + // We would want to prefer LLVM for release builds when it is available, however + // we don't have an LLVM backend yet :) + // We would also want to prefer LLVM for architectures that we don't have self-hosted support for too. + break :blk false; + }; + if (!use_llvm and options.machine_code_model != .default) { + return error.MachineCodeModelNotSupported; + } + // Make a decision on whether to use LLD or our own linker. const use_lld = if (options.use_lld) |explicit| explicit else blk: { if (!build_options.have_llvm) @@ -393,7 +421,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :blk true; } - if (build_options.is_stage1) { + if (use_llvm) { // If stage1 generates an object file, self-hosted linker is not // yet sophisticated enough to handle that. break :blk options.root_pkg != null; @@ -402,25 +430,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :blk false; }; - // Make a decision on whether to use LLVM or our own backend. - const use_llvm = if (options.use_llvm) |explicit| explicit else blk: { - // If we have no zig code to compile, no need for LLVM. - if (options.root_pkg == null) - break :blk false; - - // If we are the stage1 compiler, we depend on the stage1 c++ llvm backend - // to compile zig code. - if (build_options.is_stage1) - break :blk true; - - // We would want to prefer LLVM for release builds when it is available, however - // we don't have an LLVM backend yet :) - // We would also want to prefer LLVM for architectures that we don't have self-hosted support for too. - break :blk false; - }; - if (!use_llvm and options.machine_code_model != .default) { - return error.MachineCodeModelNotSupported; - } const link_libc = options.link_libc or (is_exe_or_dyn_lib and target_util.osRequiresLibC(options.target)); @@ -720,6 +729,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .llvm_cpu_features = llvm_cpu_features, .is_compiler_rt_or_libc = options.is_compiler_rt_or_libc, .each_lib_rpath = options.each_lib_rpath orelse false, + .disable_lld_caching = options.disable_lld_caching, }); errdefer bin_file.destroy(); comp.* = .{ @@ -2288,7 +2298,7 @@ pub fn updateSubCompilation(sub_compilation: *Compilation) !void { } } -fn buildStaticLibFromZig(comp: *Compilation, basename: []const u8, out: *?CRTFile) !void { +fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CRTFile) !void { const tracy = trace(@src()); defer tracy.end(); @@ -2304,12 +2314,20 @@ fn buildStaticLibFromZig(comp: *Compilation, basename: []const u8, out: *?CRTFil .path = special_path, .handle = special_dir, }, - .root_src_path = basename, + .root_src_path = src_basename, }; + const root_name = mem.split(src_basename, ".").next().?; + const target = comp.getTarget(); + const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{ + .root_name = root_name, + .target = target, + .output_mode = .Lib, + }); + defer comp.gpa.free(bin_basename); const emit_bin = Compilation.EmitLoc{ .directory = null, // Put it in the cache directory. - .basename = basename, + .basename = bin_basename, }; const optimize_mode: std.builtin.Mode = blk: { if (comp.is_test) @@ -2323,8 +2341,8 @@ fn buildStaticLibFromZig(comp: *Compilation, basename: []const u8, out: *?CRTFil .global_cache_directory = comp.global_cache_directory, .local_cache_directory = comp.global_cache_directory, .zig_lib_directory = comp.zig_lib_directory, - .target = comp.getTarget(), - .root_name = mem.split(basename, ".").next().?, + .target = target, + .root_name = root_name, .root_pkg = &root_pkg, .output_mode = .Lib, .rand = comp.rand, @@ -2358,7 +2376,9 @@ fn buildStaticLibFromZig(comp: *Compilation, basename: []const u8, out: *?CRTFil assert(out.* == null); out.* = Compilation.CRTFile{ - .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{basename}), + .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{ + sub_compilation.bin_file.options.sub_path, + }), .lock = sub_compilation.bin_file.toOwnedLock(), }; } @@ -2461,7 +2481,7 @@ fn updateStage1Module(comp: *Compilation) !void { ) orelse return error.OutOfMemory; const stage1_pkg = try createStage1Pkg(arena, "root", mod.root_pkg, null); - const output_dir = comp.bin_file.options.directory.path orelse "."; + const output_dir = directory.path orelse "."; const test_filter = comp.test_filter orelse ""[0..0]; const test_name_prefix = comp.test_name_prefix orelse ""[0..0]; stage1_module.* = .{ @@ -2617,13 +2637,11 @@ pub fn build_crt_file( try sub_compilation.updateSubCompilation(); try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1); - const artifact_path = if (sub_compilation.bin_file.options.directory.path) |p| - try std.fs.path.join(comp.gpa, &[_][]const u8{ p, basename }) - else - try comp.gpa.dupe(u8, basename); comp.crt_files.putAssumeCapacityNoClobber(basename, .{ - .full_object_path = artifact_path, + .full_object_path = try sub_compilation.bin_file.options.directory.join(comp.gpa, &[_][]const u8{ + sub_compilation.bin_file.options.sub_path, + }), .lock = sub_compilation.bin_file.toOwnedLock(), }); } diff --git a/src/link.zig b/src/link.zig index 82d67f2a7a..4c485063bd 100644 --- a/src/link.zig +++ b/src/link.zig @@ -66,6 +66,7 @@ pub const Options = struct { error_return_tracing: bool, is_compiler_rt_or_libc: bool, each_lib_rpath: bool, + disable_lld_caching: bool, gc_sections: ?bool = null, allow_shlib_undefined: ?bool = null, linker_script: ?[]const u8 = null, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 931c1bc9c0..82da3f3ae2 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -23,6 +23,7 @@ const File = link.File; const build_options = @import("build_options"); const target_util = @import("../target.zig"); const glibc = @import("../glibc.zig"); +const Cache = @import("../Cache.zig"); const default_entry_addr = 0x8000000; @@ -1225,7 +1226,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm; if (use_stage1) { const obj_basename = try std.fmt.allocPrint(arena, "{}.o", .{self.base.options.root_name}); - const full_obj_path = try directory.join(arena, &[_][]const u8{obj_basename}); + const o_directory = self.base.options.module.?.zig_cache_artifact_directory; + const full_obj_path = try o_directory.join(arena, &[_][]const u8{obj_basename}); break :blk full_obj_path; } @@ -1235,6 +1237,12 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { break :blk full_obj_path; } else null; + const is_lib = self.base.options.output_mode == .Lib; + const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; + const have_dynamic_linker = self.base.options.link_libc and + self.base.options.link_mode == .Dynamic and is_exe_or_dyn_lib; + // Here we want to determine whether we can save time by not invoking LLD when the // output is unchanged. None of the linker options or the object files that are being // linked are in the hash that namespaces the directory we are outputting to. Therefore, @@ -1245,78 +1253,78 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD. const id_symlink_basename = "lld.id"; - // We are about to obtain this lock, so here we give other processes a chance first. - self.base.releaseLock(); - - var ch = comp.cache_parent.obtain(); - defer ch.deinit(); + var man: Cache.Manifest = undefined; + defer if (!self.base.options.disable_lld_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!self.base.options.disable_lld_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + self.base.releaseLock(); + + try man.addOptionalFile(self.base.options.linker_script); + try man.addOptionalFile(self.base.options.version_script); + try man.addListOfFiles(self.base.options.objects); + for (comp.c_object_table.items()) |entry| { + _ = try man.addFile(entry.key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + // We can skip hashing libc and libc++ components that we are in charge of building from Zig + // installation sources because they are always a product of the compiler version + target information. + man.hash.addOptional(self.base.options.stack_size_override); + man.hash.addOptional(self.base.options.gc_sections); + man.hash.add(self.base.options.eh_frame_hdr); + man.hash.add(self.base.options.rdynamic); + man.hash.addListOfBytes(self.base.options.extra_lld_args); + man.hash.addListOfBytes(self.base.options.lib_dirs); + man.hash.addListOfBytes(self.base.options.rpath_list); + man.hash.add(self.base.options.each_lib_rpath); + man.hash.add(self.base.options.is_compiler_rt_or_libc); + man.hash.add(self.base.options.z_nodelete); + man.hash.add(self.base.options.z_defs); + if (self.base.options.link_libc) { + man.hash.add(self.base.options.libc_installation != null); + if (self.base.options.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + } + if (have_dynamic_linker) { + man.hash.addOptionalBytes(self.base.options.dynamic_linker); + } + } + if (is_dyn_lib) { + man.hash.addOptionalBytes(self.base.options.override_soname); + man.hash.addOptional(self.base.options.version); + } + man.hash.addListOfBytes(self.base.options.system_libs); + man.hash.addOptional(self.base.options.allow_shlib_undefined); + man.hash.add(self.base.options.bind_global_refs_locally); - const is_lib = self.base.options.output_mode == .Lib; - const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; - const have_dynamic_linker = self.base.options.link_libc and - self.base.options.link_mode == .Dynamic and is_exe_or_dyn_lib; + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); - try ch.addOptionalFile(self.base.options.linker_script); - try ch.addOptionalFile(self.base.options.version_script); - try ch.addListOfFiles(self.base.options.objects); - for (comp.c_object_table.items()) |entry| { - _ = try ch.addFile(entry.key.status.success.object_path, null); - } - try ch.addOptionalFile(module_obj_path); - // We can skip hashing libc and libc++ components that we are in charge of building from Zig - // installation sources because they are always a product of the compiler version + target information. - ch.hash.addOptional(self.base.options.stack_size_override); - ch.hash.addOptional(self.base.options.gc_sections); - ch.hash.add(self.base.options.eh_frame_hdr); - ch.hash.add(self.base.options.rdynamic); - ch.hash.addListOfBytes(self.base.options.extra_lld_args); - ch.hash.addListOfBytes(self.base.options.lib_dirs); - ch.hash.addListOfBytes(self.base.options.rpath_list); - ch.hash.add(self.base.options.each_lib_rpath); - ch.hash.add(self.base.options.is_compiler_rt_or_libc); - ch.hash.add(self.base.options.z_nodelete); - ch.hash.add(self.base.options.z_defs); - if (self.base.options.link_libc) { - ch.hash.add(self.base.options.libc_installation != null); - if (self.base.options.libc_installation) |libc_installation| { - ch.hash.addBytes(libc_installation.crt_dir.?); - } - if (have_dynamic_linker) { - ch.hash.addOptionalBytes(self.base.options.dynamic_linker); + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { + log.debug("ELF LLD new_digest={} readlink error: {}", .{digest, @errorName(err)}); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("ELF LLD digest={} match - skipping invocation", .{digest}); + // Hot diggity dog! The output binary is already there. + self.base.lock = man.toOwnedLock(); + return; } + log.debug("ELF LLD prev_digest={} new_digest={}", .{prev_digest, digest}); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; } - if (is_dyn_lib) { - ch.hash.addOptionalBytes(self.base.options.override_soname); - ch.hash.addOptional(self.base.options.version); - } - ch.hash.addListOfBytes(self.base.options.system_libs); - ch.hash.addOptional(self.base.options.allow_shlib_undefined); - ch.hash.add(self.base.options.bind_global_refs_locally); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try ch.hit(); - const digest = ch.final(); - - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { - log.debug("ELF LLD new_digest={} readlink error: {}", .{digest, @errorName(err)}); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("ELF LLD digest={} match - skipping invocation", .{digest}); - // Hot diggity dog! The output binary is already there. - self.base.lock = ch.toOwnedLock(); - return; - } - log.debug("ELF LLD prev_digest={} new_digest={}", .{prev_digest, digest}); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; const target = self.base.options.target; const is_obj = self.base.options.output_mode == .Obj; @@ -1620,18 +1628,20 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { std.log.warn("unexpected LLD stderr: {}", .{stderr_context.data.items}); } - // Update the dangling symlink with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { - std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - ch.writeManifest() catch |err| { - std.log.warn("failed to write cache manifest when linking: {}", .{ @errorName(err) }); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - self.base.lock = ch.toOwnedLock(); + if (!self.base.options.disable_lld_caching) { + // Update the dangling symlink with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { + std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + std.log.warn("failed to write cache manifest when linking: {}", .{ @errorName(err) }); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + self.base.lock = man.toOwnedLock(); + } } const LLDContext = struct { diff --git a/src/main.zig b/src/main.zig index fe4c27ced6..fc3ebd49c4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1425,6 +1425,7 @@ pub fn buildOutputType( .test_evented_io = test_evented_io, .test_filter = test_filter, .test_name_prefix = test_name_prefix, + .disable_lld_caching = !have_enable_cache, }) catch |err| { fatal("unable to create compilation: {}", .{@errorName(err)}); }; |
