diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index dd8c4158ce..924dc18f91 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1834,47 +1834,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void { lp.path = b.fmt("{}", .{output_dir}); } - // -femit-bin[=path] (default) Output machine code - if (compile.generated_bin) |bin| { - bin.path = output_dir.joinString(b.allocator, compile.out_filename) catch @panic("OOM"); - } - - const sep = std.fs.path.sep_str; - - // output PDB if someone requested it - if (compile.generated_pdb) |pdb| { - pdb.path = b.fmt("{}" ++ sep ++ "{s}.pdb", .{ output_dir, compile.name }); - } - - // -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL - if (compile.generated_implib) |implib| { - implib.path = b.fmt("{}" ++ sep ++ "{s}.lib", .{ output_dir, compile.name }); - } - - // -femit-h[=path] Generate a C header file (.h) - if (compile.generated_h) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.h", .{ output_dir, compile.name }); - } - - // -femit-docs[=path] Create a docs/ dir with html documentation - if (compile.generated_docs) |generated_docs| { - generated_docs.path = output_dir.joinString(b.allocator, "docs") catch @panic("OOM"); - } - - // -femit-asm[=path] Output .s (assembly code) - if (compile.generated_asm) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.s", .{ output_dir, compile.name }); - } - - // -femit-llvm-ir[=path] Produce a .ll file with optimized LLVM IR (requires LLVM extensions) - if (compile.generated_llvm_ir) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.ll", .{ output_dir, compile.name }); - } - - // -femit-llvm-bc[=path] Produce an optimized LLVM module as a .bc file (requires LLVM extensions) - if (compile.generated_llvm_bc) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.bc", .{ output_dir, compile.name }); - } + // zig fmt: off + if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin); + if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb); + if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib); + if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h); + if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs); + if (compile.generated_asm) |lp| lp.path = compile.outputPath(output_dir, .@"asm"); + if (compile.generated_llvm_ir) |lp| lp.path = compile.outputPath(output_dir, .llvm_ir); + if (compile.generated_llvm_bc) |lp| lp.path = compile.outputPath(output_dir, .llvm_bc); + // zig fmt: on } if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic and @@ -1888,6 +1857,21 @@ fn make(step: *Step, options: Step.MakeOptions) !void { ); } } +fn outputPath(c: *Compile, out_dir: std.Build.Cache.Path, ea: std.zig.EmitArtifact) []const u8 { + const arena = c.step.owner.graph.arena; + const name = ea.cacheName(arena, .{ + .root_name = c.name, + .target = c.root_module.resolved_target.?.result, + .output_mode = switch (c.kind) { + .lib => .Lib, + .obj, .test_obj => .Obj, + .exe, .@"test" => .Exe, + }, + .link_mode = c.linkage, + .version = c.version, + }) catch @panic("OOM"); + return out_dir.joinString(arena, name) catch @panic("OOM"); +} pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) !Path { const gpa = c.step.owner.allocator; diff --git a/lib/std/zig.zig b/lib/std/zig.zig index a946fe5e8b..21f1296766 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -884,6 +884,35 @@ pub const SimpleComptimeReason = enum(u32) { } }; +/// Every kind of artifact which the compiler can emit. +pub const EmitArtifact = enum { + bin, + @"asm", + implib, + llvm_ir, + llvm_bc, + docs, + pdb, + h, + + /// If using `Server` to communicate with the compiler, it will place requested artifacts in + /// paths under the output directory, where those paths are named according to this function. + /// Returned string is allocated with `gpa` and owned by the caller. + pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 { + const suffix: []const u8 = switch (ea) { + .bin => return binNameAlloc(gpa, opts), + .@"asm" => ".s", + .implib => ".lib", + .llvm_ir => ".ll", + .llvm_bc => ".bc", + .docs => "-docs", + .pdb => ".pdb", + .h => ".h", + }; + return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix }); + } +}; + test { _ = Ast; _ = AstRlAnnotate; diff --git a/src/Compilation.zig b/src/Compilation.zig index 0342566e27..b9b51222eb 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -55,8 +55,7 @@ gpa: Allocator, arena: Allocator, /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. zcu: ?*Zcu, -/// Contains different state depending on whether the Compilation uses -/// incremental or whole cache mode. +/// Contains different state depending on the `CacheMode` used by this `Compilation`. cache_use: CacheUse, /// All compilations have a root module because this is where some important /// settings are stored, such as target and optimization mode. This module @@ -67,17 +66,13 @@ root_mod: *Package.Module, config: Config, /// The main output file. -/// In whole cache mode, this is null except for during the body of the update -/// function. In incremental cache mode, this is a long-lived object. -/// In both cases, this is `null` when `-fno-emit-bin` is used. +/// In `CacheMode.whole`, this is null except for during the body of `update`. +/// In `CacheMode.none` and `CacheMode.incremental`, this is long-lived. +/// Regardless of cache mode, this is `null` when `-fno-emit-bin` is used. bin_file: ?*link.File, /// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin) sysroot: ?[]const u8, -/// This is `null` when not building a Windows DLL, or when `-fno-emit-implib` is used. -implib_emit: ?Cache.Path, -/// This is non-null when `-femit-docs` is provided. -docs_emit: ?Cache.Path, root_name: [:0]const u8, compiler_rt_strat: RtStrat, ubsan_rt_strat: RtStrat, @@ -259,10 +254,6 @@ mutex: if (builtin.single_threaded) struct { test_filters: []const []const u8, test_name_prefix: ?[]const u8, -emit_asm: ?EmitLoc, -emit_llvm_ir: ?EmitLoc, -emit_llvm_bc: ?EmitLoc, - link_task_wait_group: WaitGroup = .{}, work_queue_progress_node: std.Progress.Node = .none, @@ -274,6 +265,31 @@ file_system_inputs: ?*std.ArrayListUnmanaged(u8), /// This digest will be known after update() is called. digest: ?[Cache.bin_digest_len]u8 = null, +/// Non-`null` iff we are emitting a binary. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_bin: ?[]const u8, +/// Non-`null` iff we are emitting assembly. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_asm: ?[]const u8, +/// Non-`null` iff we are emitting an implib. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_implib: ?[]const u8, +/// Non-`null` iff we are emitting LLVM IR. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_llvm_ir: ?[]const u8, +/// Non-`null` iff we are emitting LLVM bitcode. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_llvm_bc: ?[]const u8, +/// Non-`null` iff we are emitting documentation. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_docs: ?[]const u8, + const QueuedJobs = struct { compiler_rt_lib: bool = false, compiler_rt_obj: bool = false, @@ -774,13 +790,6 @@ pub const CrtFile = struct { lock: Cache.Lock, full_object_path: Cache.Path, - pub fn isObject(cf: CrtFile) bool { - return switch (classifyFileExt(cf.full_object_path.sub_path)) { - .object => true, - else => false, - }; - } - pub fn deinit(self: *CrtFile, gpa: Allocator) void { self.lock.release(); gpa.free(self.full_object_path.sub_path); @@ -1321,14 +1330,6 @@ pub const MiscError = struct { } }; -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 `Compilation`. - directory: ?Cache.Directory, - /// This may not have sub-directories in it. - basename: []const u8, -}; - pub const cache_helpers = struct { pub fn addModule(hh: *Cache.HashHelper, mod: *const Package.Module) void { addResolvedTarget(hh, mod.resolved_target); @@ -1368,15 +1369,6 @@ pub const cache_helpers = struct { hh.add(resolved_target.is_explicit_dynamic_linker); } - pub fn addEmitLoc(hh: *Cache.HashHelper, emit_loc: EmitLoc) void { - hh.addBytes(emit_loc.basename); - } - - pub fn addOptionalEmitLoc(hh: *Cache.HashHelper, optional_emit_loc: ?EmitLoc) void { - hh.add(optional_emit_loc != null); - addEmitLoc(hh, optional_emit_loc orelse return); - } - pub fn addOptionalDebugFormat(hh: *Cache.HashHelper, x: ?Config.DebugFormat) void { hh.add(x != null); addDebugFormat(hh, x orelse return); @@ -1423,7 +1415,38 @@ pub const ClangPreprocessorMode = enum { pub const Framework = link.File.MachO.Framework; pub const SystemLib = link.SystemLib; -pub const CacheMode = enum { incremental, whole }; +pub const CacheMode = enum { + /// The results of this compilation are not cached. The compilation is always performed, and the + /// results are emitted directly to their output locations. Temporary files will be placed in a + /// temporary directory in the cache, but deleted after the compilation is done. + /// + /// This mode is typically used for direct CLI invocations like `zig build-exe`, because such + /// processes are typically low-level usages which would not make efficient use of the cache. + none, + /// The compilation is cached based only on the options given when creating the `Compilation`. + /// In particular, Zig source file contents are not included in the cache manifest. This mode + /// allows incremental compilation, because the old cached compilation state can be restored + /// and the old binary patched up with the changes. All files, including temporary files, are + /// stored in the cache directory like '/o//'. Temporary files are not deleted. + /// + /// At the time of writing, incremental compilation is only supported with the `-fincremental` + /// command line flag, so this mode is rarely used. However, it is required in order to use + /// incremental compilation. + incremental, + /// The compilation is cached based on the `Compilation` options and every input, including Zig + /// source files, linker inputs, and `@embedFile` targets. If any of them change, we will see a + /// cache miss, and the entire compilation will be re-run. On a cache miss, we initially write + /// all output files to a directory under '/tmp/', because we don't know the final + /// manifest digest until the update is almost done. Once we can compute the final digest, this + /// directory is moved to '/o//'. Temporary files are not deleted. + /// + /// At the time of writing, this is the most commonly used cache mode: it is used by the build + /// system (and any other parent using `--listen`) unless incremental compilation is enabled. + /// Once incremental compilation is more mature, it will be replaced by `incremental` in many + /// cases, but still has use cases, such as for release binaries, particularly globally cached + /// artifacts like compiler_rt. + whole, +}; pub const ParentWholeCache = struct { manifest: *Cache.Manifest, @@ -1432,22 +1455,33 @@ pub const ParentWholeCache = struct { }; const CacheUse = union(CacheMode) { + none: *None, incremental: *Incremental, whole: *Whole, + const None = struct { + /// User-requested artifacts are written directly to their output path in this cache mode. + /// However, if we need to emit any temporary files, they are placed in this directory. + /// We will recursively delete this directory at the end of this update. This field is + /// non-`null` only inside `update`. + tmp_artifact_directory: ?Cache.Directory, + }; + + const Incremental = struct { + /// All output files, including artifacts and incremental compilation metadata, are placed + /// in this directory, which is some 'o/' in a cache directory. + artifact_directory: Cache.Directory, + }; + const Whole = struct { - /// This is a pointer to a local variable inside `update()`. - cache_manifest: ?*Cache.Manifest = null, - cache_manifest_mutex: std.Thread.Mutex = .{}, - /// null means -fno-emit-bin. - /// This is mutable memory allocated into the Compilation-lifetime arena (`arena`) - /// of exactly the correct size for "o/[digest]/[basename]". - /// The basename is of the outputted binary file in case we don't know the directory yet. - bin_sub_path: ?[]u8, - /// Same as `bin_sub_path` but for implibs. - implib_sub_path: ?[]u8, - docs_sub_path: ?[]u8, + /// Since we don't open the output file until `update`, we must save these options for then. lf_open_opts: link.File.OpenOptions, + /// This is a pointer to a local variable inside `update`. + cache_manifest: ?*Cache.Manifest, + cache_manifest_mutex: std.Thread.Mutex, + /// This is non-`null` for most of the body of `update`. It is the temporary directory which + /// we initially emit our artifacts to. After the main part of the update is done, it will + /// be closed and moved to its final location, and this field set to `null`. tmp_artifact_directory: ?Cache.Directory, /// Prevents other processes from clobbering files in the output directory. lock: ?Cache.Lock, @@ -1466,17 +1500,16 @@ const CacheUse = union(CacheMode) { } }; - const Incremental = struct { - /// Where build artifacts and incremental compilation metadata serialization go. - artifact_directory: Cache.Directory, - }; - fn deinit(cu: CacheUse) void { switch (cu) { + .none => |none| { + assert(none.tmp_artifact_directory == null); + }, .incremental => |incremental| { incremental.artifact_directory.handle.close(); }, .whole => |whole| { + assert(whole.tmp_artifact_directory == null); whole.releaseLock(); }, } @@ -1503,28 +1536,14 @@ pub const CreateOptions = struct { std_mod: ?*Package.Module = null, root_name: []const u8, sysroot: ?[]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, - /// `null` means to not emit assembly. - emit_asm: ?EmitLoc = null, - /// `null` means to not emit LLVM IR. - emit_llvm_ir: ?EmitLoc = null, - /// `null` means to not emit LLVM module bitcode. - emit_llvm_bc: ?EmitLoc = null, - /// `null` means to not emit docs. - emit_docs: ?EmitLoc = null, - /// `null` means to not emit an import lib. - emit_implib: ?EmitLoc = null, - /// 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, - cache_mode: CacheMode = .incremental, + cache_mode: CacheMode, + emit_h: Emit = .no, + emit_bin: Emit, + emit_asm: Emit = .no, + emit_implib: Emit = .no, + emit_llvm_ir: Emit = .no, + emit_llvm_bc: Emit = .no, + emit_docs: Emit = .no, /// This field is intended to be removed. /// The ELF implementation no longer uses this data, however the MachO and COFF /// implementations still do. @@ -1662,6 +1681,38 @@ pub const CreateOptions = struct { parent_whole_cache: ?ParentWholeCache = null, pub const Entry = link.File.OpenOptions.Entry; + + /// Which fields are valid depends on the `cache_mode` given. + pub const Emit = union(enum) { + /// Do not emit this file. Always valid. + no, + /// Emit this file into its default name in the cache directory. + /// Requires `cache_mode` to not be `.none`. + yes_cache, + /// Emit this file to the given path (absolute or cwd-relative). + /// Requires `cache_mode` to be `.none`. + yes_path: []const u8, + + fn resolve(emit: Emit, arena: Allocator, opts: *const CreateOptions, ea: std.zig.EmitArtifact) Allocator.Error!?[]const u8 { + switch (emit) { + .no => return null, + .yes_cache => { + assert(opts.cache_mode != .none); + return try ea.cacheName(arena, .{ + .root_name = opts.root_name, + .target = opts.root_mod.resolved_target.result, + .output_mode = opts.config.output_mode, + .link_mode = opts.config.link_mode, + .version = opts.version, + }); + }, + .yes_path => |path| { + assert(opts.cache_mode == .none); + return try arena.dupe(u8, path); + }, + } + } + }; }; fn addModuleTableToCacheHash( @@ -1869,13 +1920,18 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil cache.hash.add(options.config.link_libunwind); cache.hash.add(output_mode); cache_helpers.addDebugFormat(&cache.hash, options.config.debug_format); - cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_bin); - cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_implib); - cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_docs); cache.hash.addBytes(options.root_name); cache.hash.add(options.config.wasi_exec_model); cache.hash.add(options.config.san_cov_trace_pc_guard); cache.hash.add(options.debug_compiler_runtime_libs); + // The actual emit paths don't matter. They're only user-specified if we aren't using the + // cache! However, it does matter whether the files are emitted at all. + cache.hash.add(options.emit_bin != .no); + cache.hash.add(options.emit_asm != .no); + cache.hash.add(options.emit_implib != .no); + cache.hash.add(options.emit_llvm_ir != .no); + cache.hash.add(options.emit_llvm_bc != .no); + cache.hash.add(options.emit_docs != .no); // TODO audit this and make sure everything is in it const main_mod = options.main_mod orelse options.root_mod; @@ -1925,7 +1981,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil try zcu.init(options.thread_pool.getIdCount()); break :blk zcu; } else blk: { - if (options.emit_h != null) return error.NoZigModuleForCHeader; + if (options.emit_h != .no) return error.NoZigModuleForCHeader; break :blk null; }; errdefer if (opt_zcu) |zcu| zcu.deinit(); @@ -1938,18 +1994,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .arena = arena, .zcu = opt_zcu, .cache_use = undefined, // populated below - .bin_file = null, // populated below - .implib_emit = null, // handled below - .docs_emit = null, // handled below + .bin_file = null, // populated below if necessary .root_mod = options.root_mod, .config = options.config, .dirs = options.dirs, - .emit_asm = options.emit_asm, - .emit_llvm_ir = options.emit_llvm_ir, - .emit_llvm_bc = options.emit_llvm_bc, .work_queues = @splat(.init(gpa)), - .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa), - .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{}, + .c_object_work_queue = .init(gpa), + .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) .init(gpa) else .{}, .c_source_files = options.c_source_files, .rc_source_files = options.rc_source_files, .cache_parent = cache, @@ -2002,6 +2053,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .file_system_inputs = options.file_system_inputs, .parent_whole_cache = options.parent_whole_cache, .link_diags = .init(gpa), + .emit_bin = try options.emit_bin.resolve(arena, &options, .bin), + .emit_asm = try options.emit_asm.resolve(arena, &options, .@"asm"), + .emit_implib = try options.emit_implib.resolve(arena, &options, .implib), + .emit_llvm_ir = try options.emit_llvm_ir.resolve(arena, &options, .llvm_ir), + .emit_llvm_bc = try options.emit_llvm_bc.resolve(arena, &options, .llvm_bc), + .emit_docs = try options.emit_docs.resolve(arena, &options, .docs), }; // Prevent some footguns by making the "any" fields of config reflect @@ -2068,7 +2125,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .soname = options.soname, .compatibility_version = options.compatibility_version, .build_id = build_id, - .disable_lld_caching = options.disable_lld_caching or options.cache_mode == .whole, .subsystem = options.subsystem, .hash_style = options.hash_style, .enable_link_snapshots = options.enable_link_snapshots, @@ -2087,6 +2143,17 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; switch (options.cache_mode) { + .none => { + const none = try arena.create(CacheUse.None); + none.* = .{ .tmp_artifact_directory = null }; + comp.cache_use = .{ .none = none }; + if (comp.emit_bin) |path| { + comp.bin_file = try link.File.open(arena, comp, .{ + .root_dir = .cwd(), + .sub_path = path, + }, lf_open_opts); + } + }, .incremental => { // Options that are specific to zig source files, that cannot be // modified between incremental updates. @@ -2100,7 +2167,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil hash.addListOfBytes(options.test_filters); hash.addOptionalBytes(options.test_name_prefix); hash.add(options.skip_linker_dependencies); - hash.add(options.emit_h != null); + hash.add(options.emit_h != .no); hash.add(error_limit); // Here we put the root source file path name, but *not* with addFile. @@ -2135,49 +2202,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; comp.cache_use = .{ .incremental = incremental }; - if (options.emit_bin) |emit_bin| { + if (comp.emit_bin) |cache_rel_path| { const emit: Cache.Path = .{ - .root_dir = emit_bin.directory orelse artifact_directory, - .sub_path = emit_bin.basename, + .root_dir = artifact_directory, + .sub_path = cache_rel_path, }; comp.bin_file = try link.File.open(arena, comp, emit, lf_open_opts); } - - if (options.emit_implib) |emit_implib| { - comp.implib_emit = .{ - .root_dir = emit_implib.directory orelse artifact_directory, - .sub_path = emit_implib.basename, - }; - } - - if (options.emit_docs) |emit_docs| { - comp.docs_emit = .{ - .root_dir = emit_docs.directory orelse artifact_directory, - .sub_path = emit_docs.basename, - }; - } }, .whole => { - // For whole cache mode, we don't know where to put outputs from - // the linker until the final cache hash, which is available after - // the compilation is complete. + // For whole cache mode, we don't know where to put outputs from the linker until + // the final cache hash, which is available after the compilation is complete. // - // Therefore, bin_file is left null until the beginning of update(), - // where it may find a cache hit, or use a temporary directory to - // hold output artifacts. + // Therefore, `comp.bin_file` is left `null` (already done) until `update`, where + // it may find a cache hit, or else will use a temporary directory to hold output + // artifacts. const whole = try arena.create(CacheUse.Whole); whole.* = .{ - // This is kept here so that link.File.open can be called later. .lf_open_opts = lf_open_opts, - // This is so that when doing `CacheMode.whole`, the mechanism in update() - // can use it for communicating the result directory via `bin_file.emit`. - // This is used to distinguish between -fno-emit-bin and -femit-bin - // for `CacheMode.whole`. - // This memory will be overwritten with the real digest in update() but - // the basename will be preserved. - .bin_sub_path = try prepareWholeEmitSubPath(arena, options.emit_bin), - .implib_sub_path = try prepareWholeEmitSubPath(arena, options.emit_implib), - .docs_sub_path = try prepareWholeEmitSubPath(arena, options.emit_docs), + .cache_manifest = null, + .cache_manifest_mutex = .{}, .tmp_artifact_directory = null, .lock = null, }; @@ -2245,12 +2289,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } } - const have_bin_emit = switch (comp.cache_use) { - .whole => |whole| whole.bin_sub_path != null, - .incremental => comp.bin_file != null, - }; - - if (have_bin_emit and target.ofmt != .c) { + if (comp.emit_bin != null and target.ofmt != .c) { if (!comp.skip_linker_dependencies) { // If we need to build libc for the target, add work items for it. // We go through the work queue so that building can be done in parallel. @@ -2544,8 +2583,23 @@ pub fn hotCodeSwap( try lf.makeExecutable(); } -fn cleanupAfterUpdate(comp: *Compilation) void { +fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { switch (comp.cache_use) { + .none => |none| { + if (none.tmp_artifact_directory) |*tmp_dir| { + tmp_dir.handle.close(); + none.tmp_artifact_directory = null; + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { + log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ + comp.dirs.local_cache.path orelse ".", + std.fs.path.sep, + tmp_dir_sub_path, + @errorName(err), + }); + }; + } + }, .incremental => return, .whole => |whole| { if (whole.cache_manifest) |man| { @@ -2556,10 +2610,18 @@ fn cleanupAfterUpdate(comp: *Compilation) void { lf.destroy(); comp.bin_file = null; } - if (whole.tmp_artifact_directory) |*directory| { - directory.handle.close(); - if (directory.path) |p| comp.gpa.free(p); + if (whole.tmp_artifact_directory) |*tmp_dir| { + tmp_dir.handle.close(); whole.tmp_artifact_directory = null; + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { + log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ + comp.dirs.local_cache.path orelse ".", + std.fs.path.sep, + tmp_dir_sub_path, + @errorName(err), + }); + }; } }, } @@ -2579,14 +2641,27 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { comp.clearMiscFailures(); comp.last_update_was_cache_hit = false; - var man: Cache.Manifest = undefined; - defer cleanupAfterUpdate(comp); - var tmp_dir_rand_int: u64 = undefined; + var man: Cache.Manifest = undefined; + defer cleanupAfterUpdate(comp, tmp_dir_rand_int); // If using the whole caching strategy, we check for *everything* up front, including // C source files. + log.debug("Compilation.update for {s}, CacheMode.{s}", .{ comp.root_name, @tagName(comp.cache_use) }); switch (comp.cache_use) { + .none => |none| { + assert(none.tmp_artifact_directory == null); + none.tmp_artifact_directory = d: { + tmp_dir_rand_int = std.crypto.random.int(u64); + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); + break :d .{ + .path = path, + .handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}), + }; + }; + }, + .incremental => {}, .whole => |whole| { assert(comp.bin_file == null); // We are about to obtain this lock, so here we give other processes a chance first. @@ -2633,10 +2708,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { comp.last_update_was_cache_hit = true; log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name}); const bin_digest = man.finalBin(); - const hex_digest = Cache.binToHex(bin_digest); comp.digest = bin_digest; - comp.wholeCacheModeSetBinFilePath(whole, &hex_digest); assert(whole.lock == null); whole.lock = man.toOwnedLock(); @@ -2645,52 +2718,23 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { log.debug("CacheMode.whole cache miss for {s}", .{comp.root_name}); // Compile the artifacts to a temporary directory. - const tmp_artifact_directory: Cache.Directory = d: { - const s = std.fs.path.sep_str; + whole.tmp_artifact_directory = d: { tmp_dir_rand_int = std.crypto.random.int(u64); - const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); - - const path = try comp.dirs.local_cache.join(gpa, &.{tmp_dir_sub_path}); - errdefer gpa.free(path); - - const handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); - errdefer handle.close(); - + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); break :d .{ .path = path, - .handle = handle, + .handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}), }; }; - whole.tmp_artifact_directory = tmp_artifact_directory; - - // Now that the directory is known, it is time to create the Emit - // objects and call link.File.open. - - if (whole.implib_sub_path) |sub_path| { - comp.implib_emit = .{ - .root_dir = tmp_artifact_directory, - .sub_path = std.fs.path.basename(sub_path), - }; - } - - if (whole.docs_sub_path) |sub_path| { - comp.docs_emit = .{ - .root_dir = tmp_artifact_directory, - .sub_path = std.fs.path.basename(sub_path), - }; - } - - if (whole.bin_sub_path) |sub_path| { + if (comp.emit_bin) |sub_path| { const emit: Cache.Path = .{ - .root_dir = tmp_artifact_directory, - .sub_path = std.fs.path.basename(sub_path), + .root_dir = whole.tmp_artifact_directory.?, + .sub_path = sub_path, }; comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts); } }, - .incremental => { - log.debug("Compilation.update for {s}, CacheMode.incremental", .{comp.root_name}); - }, } // From this point we add a preliminary set of file system inputs that @@ -2789,11 +2833,18 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { return; } - // Flush below handles -femit-bin but there is still -femit-llvm-ir, - // -femit-llvm-bc, and -femit-asm, in the case of C objects. - comp.emitOthers(); + if (comp.zcu == null and comp.config.output_mode == .Obj and comp.c_object_table.count() == 1) { + // This is `zig build-obj foo.c`. We can emit asm and LLVM IR/bitcode. + const c_obj_path = comp.c_object_table.keys()[0].status.success.object_path; + if (comp.emit_asm) |path| try comp.emitFromCObject(arena, c_obj_path, ".s", path); + if (comp.emit_llvm_ir) |path| try comp.emitFromCObject(arena, c_obj_path, ".ll", path); + if (comp.emit_llvm_bc) |path| try comp.emitFromCObject(arena, c_obj_path, ".bc", path); + } switch (comp.cache_use) { + .none, .incremental => { + try flush(comp, arena, .main, main_progress_node); + }, .whole => |whole| { if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); if (comp.parent_whole_cache) |pwc| { @@ -2805,18 +2856,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { const bin_digest = man.finalBin(); const hex_digest = Cache.binToHex(bin_digest); - // Rename the temporary directory into place. - // Close tmp dir and link.File to avoid open handle during rename. - if (whole.tmp_artifact_directory) |*tmp_directory| { - tmp_directory.handle.close(); - if (tmp_directory.path) |p| gpa.free(p); - whole.tmp_artifact_directory = null; - } else unreachable; - - const s = std.fs.path.sep_str; - const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); - const o_sub_path = "o" ++ s ++ hex_digest; - // Work around windows `AccessDenied` if any files within this // directory are open by closing and reopening the file handles. const need_writable_dance: enum { no, lf_only, lf_and_debug } = w: { @@ -2841,6 +2880,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { break :w .no; }; + // Rename the temporary directory into place. + // Close tmp dir and link.File to avoid open handle during rename. + whole.tmp_artifact_directory.?.handle.close(); + whole.tmp_artifact_directory = null; + const s = std.fs.path.sep_str; + const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); + const o_sub_path = "o" ++ s ++ hex_digest; renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| { return comp.setMiscFailure( .rename_results, @@ -2853,7 +2899,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { ); }; comp.digest = bin_digest; - comp.wholeCacheModeSetBinFilePath(whole, &hex_digest); // The linker flush functions need to know the final output path // for debug info purposes because executable debug info contains @@ -2861,10 +2906,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { if (comp.bin_file) |lf| { lf.emit = .{ .root_dir = comp.dirs.local_cache, - .sub_path = whole.bin_sub_path.?, + .sub_path = try std.fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }), }; - // Has to be after the `wholeCacheModeSetBinFilePath` above. switch (need_writable_dance) { .no => {}, .lf_only => try lf.makeWritable(), @@ -2875,10 +2919,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } - try flush(comp, arena, .{ - .root_dir = comp.dirs.local_cache, - .sub_path = o_sub_path, - }, .main, main_progress_node); + try flush(comp, arena, .main, main_progress_node); // Calling `flush` may have produced errors, in which case the // cache manifest must not be written. @@ -2897,11 +2938,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { assert(whole.lock == null); whole.lock = man.toOwnedLock(); }, - .incremental => |incremental| { - try flush(comp, arena, .{ - .root_dir = incremental.artifact_directory, - }, .main, main_progress_node); - }, } } @@ -2931,10 +2967,47 @@ pub fn appendFileSystemInput(comp: *Compilation, path: Compilation.Path) Allocat fsi.appendSliceAssumeCapacity(path.sub_path); } +fn resolveEmitPath(comp: *Compilation, path: []const u8) Cache.Path { + return .{ + .root_dir = switch (comp.cache_use) { + .none => .cwd(), + .incremental => |i| i.artifact_directory, + .whole => |w| w.tmp_artifact_directory.?, + }, + .sub_path = path, + }; +} +/// Like `resolveEmitPath`, but for calling during `flush`. The returned `Cache.Path` may reference +/// memory from `arena`, and may reference `path` itself. +/// If `kind == .temp`, then the returned path will be in a temporary or cache directory. This is +/// useful for intermediate files, such as the ZCU object file emitted by the LLVM backend. +pub fn resolveEmitPathFlush( + comp: *Compilation, + arena: Allocator, + kind: enum { temp, artifact }, + path: []const u8, +) Allocator.Error!Cache.Path { + switch (comp.cache_use) { + .none => |none| return .{ + .root_dir = switch (kind) { + .temp => none.tmp_artifact_directory.?, + .artifact => .cwd(), + }, + .sub_path = path, + }, + .incremental, .whole => return .{ + .root_dir = comp.dirs.local_cache, + .sub_path = try fs.path.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + path, + }), + }, + } +} fn flush( comp: *Compilation, arena: Allocator, - default_artifact_directory: Cache.Path, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) !void { @@ -2942,19 +3015,32 @@ fn flush( if (zcu.llvm_object) |llvm_object| { // Emit the ZCU object from LLVM now; it's required to flush the output file. // If there's an output file, it wants to decide where the LLVM object goes! - const zcu_obj_emit_loc: ?EmitLoc = if (comp.bin_file) |lf| .{ - .directory = null, - .basename = lf.zcu_object_sub_path.?, - } else null; const sub_prog_node = prog_node.start("LLVM Emit Object", 0); defer sub_prog_node.end(); try llvm_object.emit(.{ .pre_ir_path = comp.verbose_llvm_ir, .pre_bc_path = comp.verbose_llvm_bc, - .bin_path = try resolveEmitLoc(arena, default_artifact_directory, zcu_obj_emit_loc), - .asm_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_asm), - .post_ir_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_ir), - .post_bc_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_bc), + + .bin_path = p: { + const lf = comp.bin_file orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .temp, lf.zcu_object_basename.?); + break :p try p.toStringZ(arena); + }, + .asm_path = p: { + const raw = comp.emit_asm orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .artifact, raw); + break :p try p.toStringZ(arena); + }, + .post_ir_path = p: { + const raw = comp.emit_llvm_ir orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .artifact, raw); + break :p try p.toStringZ(arena); + }, + .post_bc_path = p: { + const raw = comp.emit_llvm_bc orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .artifact, raw); + break :p try p.toStringZ(arena); + }, .is_debug = comp.root_mod.optimize_mode == .Debug, .is_small = comp.root_mod.optimize_mode == .ReleaseSmall, @@ -3025,45 +3111,6 @@ fn renameTmpIntoCache( } } -/// Communicate the output binary location to parent Compilations. -fn wholeCacheModeSetBinFilePath( - comp: *Compilation, - whole: *CacheUse.Whole, - digest: *const [Cache.hex_digest_len]u8, -) void { - const digest_start = 2; // "o/[digest]/[basename]" - - if (whole.bin_sub_path) |sub_path| { - @memcpy(sub_path[digest_start..][0..digest.len], digest); - } - - if (whole.implib_sub_path) |sub_path| { - @memcpy(sub_path[digest_start..][0..digest.len], digest); - - comp.implib_emit = .{ - .root_dir = comp.dirs.local_cache, - .sub_path = sub_path, - }; - } - - if (whole.docs_sub_path) |sub_path| { - @memcpy(sub_path[digest_start..][0..digest.len], digest); - - comp.docs_emit = .{ - .root_dir = comp.dirs.local_cache, - .sub_path = sub_path, - }; - } -} - -fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemory}!?[]u8 { - const emit = opt_emit orelse return null; - if (emit.directory != null) return null; - const s = std.fs.path.sep_str; - const format = "o" ++ s ++ ("x" ** Cache.hex_digest_len) ++ s ++ "{s}"; - return try std.fmt.allocPrint(arena, format, .{emit.basename}); -} - /// This is only observed at compile-time and used to emit a compile error /// to remind the programmer to update multiple related pieces of code that /// are in different locations. Bump this number when adding or deleting @@ -3084,7 +3131,7 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addListOfBytes(comp.test_filters); man.hash.addOptionalBytes(comp.test_name_prefix); man.hash.add(comp.skip_linker_dependencies); - //man.hash.add(zcu.emit_h != null); + //man.hash.add(zcu.emit_h != .no); man.hash.add(zcu.error_limit); } else { cache_helpers.addModule(&man.hash, comp.root_mod); @@ -3130,10 +3177,6 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addListOfBytes(comp.framework_dirs); man.hash.addListOfBytes(comp.windows_libs.keys()); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc); - man.hash.addListOfBytes(comp.global_cc_argv); const opts = comp.cache_use.whole.lf_open_opts; @@ -3211,54 +3254,39 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addOptional(opts.minor_subsystem_version); } -fn emitOthers(comp: *Compilation) void { - if (comp.config.output_mode != .Obj or comp.zcu != null or - comp.c_object_table.count() == 0) - { - return; - } - const obj_path = comp.c_object_table.keys()[0].status.success.object_path; - const ext = std.fs.path.extension(obj_path.sub_path); - const dirname = obj_path.sub_path[0 .. obj_path.sub_path.len - ext.len]; - // This obj path always ends with the object file extension, but if we change the - // extension to .ll, .bc, or .s, then it will be the path to those things. - const outs = [_]struct { - emit: ?EmitLoc, - ext: []const u8, - }{ - .{ .emit = comp.emit_asm, .ext = ".s" }, - .{ .emit = comp.emit_llvm_ir, .ext = ".ll" }, - .{ .emit = comp.emit_llvm_bc, .ext = ".bc" }, - }; - for (outs) |out| { - if (out.emit) |loc| { - if (loc.directory) |directory| { - const src_path = std.fmt.allocPrint(comp.gpa, "{s}{s}", .{ - dirname, out.ext, - }) catch |err| { - log.err("unable to copy {s}{s}: {s}", .{ dirname, out.ext, @errorName(err) }); - continue; - }; - defer comp.gpa.free(src_path); - obj_path.root_dir.handle.copyFile(src_path, directory.handle, loc.basename, .{}) catch |err| { - log.err("unable to copy {s}: {s}", .{ src_path, @errorName(err) }); - }; - } - } - } -} - -fn resolveEmitLoc( +fn emitFromCObject( + comp: *Compilation, arena: Allocator, - default_artifact_directory: Cache.Path, - opt_loc: ?EmitLoc, -) Allocator.Error!?[*:0]const u8 { - const loc = opt_loc orelse return null; - const slice = if (loc.directory) |directory| - try directory.joinZ(arena, &.{loc.basename}) - else - try default_artifact_directory.joinStringZ(arena, loc.basename); - return slice.ptr; + c_obj_path: Cache.Path, + new_ext: []const u8, + unresolved_emit_path: []const u8, +) Allocator.Error!void { + // The dirname and stem (i.e. everything but the extension), of the sub path of the C object. + // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc). + const c_obj_dir_and_stem: []const u8 = p: { + const p = c_obj_path.sub_path; + const ext_len = fs.path.extension(p).len; + break :p p[0 .. p.len - ext_len]; + }; + const src_path: Cache.Path = .{ + .root_dir = c_obj_path.root_dir, + .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ + c_obj_dir_and_stem, + new_ext, + }), + }; + const emit_path = comp.resolveEmitPath(unresolved_emit_path); + + src_path.root_dir.handle.copyFile( + src_path.sub_path, + emit_path.root_dir.handle, + emit_path.sub_path, + .{}, + ) catch |err| log.err("unable to copy '{}' to '{}': {s}", .{ + src_path, + emit_path, + @errorName(err), + }); } /// Having the file open for writing is problematic as far as executing the @@ -4179,7 +4207,7 @@ fn performAllTheWorkInner( comp.link_task_queue.start(comp); - if (comp.docs_emit != null) { + if (comp.emit_docs != null) { dev.check(.docs_emit); comp.thread_pool.spawnWg(&work_queue_wait_group, workerDocsCopy, .{comp}); work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, main_progress_node }); @@ -4457,7 +4485,7 @@ fn performAllTheWorkInner( }; } }, - .incremental => {}, + .none, .incremental => {}, } if (any_fatal_files or @@ -4721,12 +4749,12 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { const zcu = comp.zcu orelse return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{}); - const emit = comp.docs_emit.?; - var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { + const docs_path = comp.resolveEmitPath(comp.emit_docs.?); + var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, - "unable to create output directory '{}{s}': {s}", - .{ emit.root_dir, emit.sub_path, @errorName(err) }, + "unable to create output directory '{}': {s}", + .{ docs_path, @errorName(err) }, ); }; defer out_dir.close(); @@ -4745,8 +4773,8 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, - "unable to create '{}{s}/sources.tar': {s}", - .{ emit.root_dir, emit.sub_path, @errorName(err) }, + "unable to create '{}/sources.tar': {s}", + .{ docs_path, @errorName(err) }, ); }; defer tar_file.close(); @@ -4896,11 +4924,6 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye .parent = root_mod, }); try root_mod.deps.put(arena, "Walk", walk_mod); - const bin_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = resolved_target.result, - .output_mode = output_mode, - }); const sub_compilation = try Compilation.create(gpa, arena, .{ .dirs = dirs, @@ -4912,10 +4935,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = .{ - .directory = null, // Put it in the cache directory. - .basename = bin_basename, - }, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, @@ -4930,27 +4950,31 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node); - const emit = comp.docs_emit.?; - var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { + var crt_file = try sub_compilation.toCrtFile(); + defer crt_file.deinit(gpa); + + const docs_bin_file = crt_file.full_object_path; + assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory + + const docs_path = comp.resolveEmitPath(comp.emit_docs.?); + var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, - "unable to create output directory '{}{s}': {s}", - .{ emit.root_dir, emit.sub_path, @errorName(err) }, + "unable to create output directory '{}': {s}", + .{ docs_path, @errorName(err) }, ); }; defer out_dir.close(); - sub_compilation.dirs.local_cache.handle.copyFile( - sub_compilation.cache_use.whole.bin_sub_path.?, + crt_file.full_object_path.root_dir.handle.copyFile( + crt_file.full_object_path.sub_path, out_dir, "main.wasm", .{}, ) catch |err| { - return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}{s}' to '{}{s}': {s}", .{ - sub_compilation.dirs.local_cache, - sub_compilation.cache_use.whole.bin_sub_path.?, - emit.root_dir, - emit.sub_path, + return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}' to '{}': {s}", .{ + crt_file.full_object_path, + docs_path, @errorName(err), }); }; @@ -5212,7 +5236,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module defer whole.cache_manifest_mutex.unlock(); try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); }, - .incremental => {}, + .incremental, .none => {}, } const bin_digest = man.finalBin(); @@ -5557,9 +5581,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr defer man.deinit(); man.hash.add(comp.clang_preprocessor_mode); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc); + man.hash.addOptionalBytes(comp.emit_asm); + man.hash.addOptionalBytes(comp.emit_llvm_ir); + man.hash.addOptionalBytes(comp.emit_llvm_bc); try cache_helpers.hashCSource(&man, c_object.src); @@ -5793,7 +5817,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); } }, - .incremental => {}, + .incremental, .none => {}, } } @@ -6037,7 +6061,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 defer whole.cache_manifest_mutex.unlock(); try whole_cache_manifest.addFilePost(dep_file_path); }, - .incremental => {}, + .incremental, .none => {}, } } } @@ -7209,12 +7233,6 @@ fn buildOutputFromZig( .cc_argv = &.{}, .parent = null, }); - const target = comp.getTarget(); - const bin_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - }); const parent_whole_cache: ?ParentWholeCache = switch (comp.cache_use) { .whole => |whole| .{ @@ -7227,7 +7245,7 @@ fn buildOutputFromZig( 3, // global cache is the same }, }, - .incremental => null, + .incremental, .none => null, }; const sub_compilation = try Compilation.create(gpa, arena, .{ @@ -7240,13 +7258,9 @@ fn buildOutputFromZig( .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = .{ - .directory = null, // Put it in the cache directory. - .basename = bin_basename, - }, + .emit_bin = .yes_cache, .function_sections = true, .data_sections = true, - .emit_h = null, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, @@ -7366,13 +7380,9 @@ pub fn build_crt_file( .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = .{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }, + .emit_bin = .yes_cache, .function_sections = options.function_sections orelse false, .data_sections = options.data_sections orelse false, - .emit_h = null, .c_source_files = c_source_files, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, @@ -7444,7 +7454,11 @@ pub fn toCrtFile(comp: *Compilation) Allocator.Error!CrtFile { return .{ .full_object_path = .{ .root_dir = comp.dirs.local_cache, - .sub_path = try comp.gpa.dupe(u8, comp.cache_use.whole.bin_sub_path.?), + .sub_path = try std.fs.path.join(comp.gpa, &.{ + "o", + &Cache.binToHex(comp.digest.?), + comp.emit_bin.?, + }), }, .lock = comp.cache_use.whole.moveLock(), }; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index ffc103310b..5215f787ef 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -2493,7 +2493,7 @@ fn newEmbedFile( cache: { const whole = switch (zcu.comp.cache_use) { .whole => |whole| whole, - .incremental => break :cache, + .incremental, .none => break :cache, }; const man = whole.cache_manifest orelse break :cache; const ip_str = opt_ip_str orelse break :cache; // this will be a compile error @@ -3377,7 +3377,7 @@ pub fn populateTestFunctions( } // The linker thread is not running, so we actually need to dispatch this task directly. - @import("../link.zig").doZcuTask(zcu.comp, @intFromEnum(pt.tid), .{ .link_nav = nav_index }); + @import("../link.zig").linkTestFunctionsNav(pt, nav_index); } } diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index d90ba974fc..0d14b6fb47 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -1019,10 +1019,6 @@ fn buildSharedLib( defer tracy.end(); const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover }); - const emit_bin = Compilation.EmitLoc{ - .directory = bin_directory, - .basename = basename, - }; const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; @@ -1082,8 +1078,7 @@ fn buildSharedLib( .root_mod = root_mod, .root_name = lib.name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index ed5eae377f..cb8dd4b460 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -1185,10 +1185,6 @@ fn buildSharedLib( defer tracy.end(); const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover }); - const emit_bin = Compilation.EmitLoc{ - .directory = bin_directory, - .basename = basename, - }; const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; @@ -1248,8 +1244,7 @@ fn buildSharedLib( .root_mod = root_mod, .root_name = lib.name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/libs/libcxx.zig b/src/libs/libcxx.zig index eb9f5df855..0009bfe120 100644 --- a/src/libs/libcxx.zig +++ b/src/libs/libcxx.zig @@ -122,17 +122,6 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError! const output_mode = .Lib; const link_mode = .static; const target = comp.root_mod.resolved_target.result; - const basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - .link_mode = link_mode, - }); - - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" }); const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" }); @@ -271,8 +260,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError! .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .c_source_files = c_source_files.items, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, @@ -327,17 +315,6 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr const output_mode = .Lib; const link_mode = .static; const target = comp.root_mod.resolved_target.result; - const basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - .link_mode = link_mode, - }); - - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" }); const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" }); @@ -467,8 +444,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .c_source_files = c_source_files.items, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, diff --git a/src/libs/libtsan.zig b/src/libs/libtsan.zig index 0c59d85bc5..f2cd6831f7 100644 --- a/src/libs/libtsan.zig +++ b/src/libs/libtsan.zig @@ -45,11 +45,6 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo .link_mode = link_mode, }); - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; - const optimize_mode = comp.compilerRtOptMode(); const strip = comp.compilerRtStrip(); const unwind_tables: std.builtin.UnwindTables = @@ -287,8 +282,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo .root_mod = root_mod, .root_name = root_name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .c_source_files = c_source_files.items, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, diff --git a/src/libs/libunwind.zig b/src/libs/libunwind.zig index ccea649c17..711d63ebbc 100644 --- a/src/libs/libunwind.zig +++ b/src/libs/libunwind.zig @@ -31,7 +31,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr const unwind_tables: std.builtin.UnwindTables = if (target.cpu.arch == .x86 and target.os.tag == .windows) .none else .@"async"; const config = Compilation.Config.resolve(.{ - .output_mode = .Lib, + .output_mode = output_mode, .resolved_target = comp.root_mod.resolved_target, .is_test = false, .have_zcu = false, @@ -85,17 +85,6 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr }; const root_name = "unwind"; - const link_mode = .static; - const basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - .link_mode = link_mode, - }); - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; var c_source_files: [unwind_src_list.len]Compilation.CSourceFile = undefined; for (unwind_src_list, 0..) |unwind_src, i| { var cflags = std.ArrayList([]const u8).init(arena); @@ -160,7 +149,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .main_mod = null, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, + .emit_bin = .yes_cache, .function_sections = comp.function_sections, .c_source_files = &c_source_files, .verbose_cc = comp.verbose_cc, diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 21aeee98b5..7c4e71c974 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -252,8 +252,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro .thread_pool = comp.thread_pool, .root_name = "c", .libc_installation = comp.libc_installation, - .emit_bin = .{ .directory = null, .basename = "libc.so" }, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 7121c308f5..f19c528d5d 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -684,10 +684,6 @@ fn buildSharedLib( defer tracy.end(); const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover }); - const emit_bin = Compilation.EmitLoc{ - .directory = bin_directory, - .basename = basename, - }; const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; @@ -746,8 +742,7 @@ fn buildSharedLib( .root_mod = root_mod, .root_name = lib.name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/link.zig b/src/link.zig index 577d7ba82c..bbd0163d23 100644 --- a/src/link.zig +++ b/src/link.zig @@ -384,9 +384,11 @@ pub const File = struct { emit: Path, file: ?fs.File, - /// When linking with LLD, this linker code will output an object file only at - /// this location, and then this path can be placed on the LLD linker line. - zcu_object_sub_path: ?[]const u8 = null, + /// When using the LLVM backend, the emitted object is written to a file with this name. This + /// object file then becomes a normal link input to LLD or a self-hosted linker. + /// + /// To convert this to an actual path, see `Compilation.resolveEmitPath` (with `kind == .temp`). + zcu_object_basename: ?[]const u8 = null, gc_sections: bool, print_gc_sections: bool, build_id: std.zig.BuildId, @@ -433,7 +435,6 @@ pub const File = struct { export_symbol_names: []const []const u8, global_base: ?u64, build_id: std.zig.BuildId, - disable_lld_caching: bool, hash_style: Lld.Elf.HashStyle, sort_section: ?Lld.Elf.SortSection, major_subsystem_version: ?u16, @@ -1083,7 +1084,7 @@ pub const File = struct { // In this case, an object file is created by the LLVM backend, so // there is no prelink phase. The Zig code is linked as a standard // object along with the others. - if (base.zcu_object_sub_path != null) return; + if (base.zcu_object_basename != null) return; switch (base.tag) { inline .wasm => |tag| { @@ -1496,6 +1497,31 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { }, } } +/// After the main pipeline is done, but before flush, the compilation may need to link one final +/// `Nav` into the binary: the `builtin.test_functions` value. Since the link thread isn't running +/// by then, we expose this function which can be called directly. +pub fn linkTestFunctionsNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) void { + const zcu = pt.zcu; + const comp = zcu.comp; + const diags = &comp.link_diags; + if (zcu.llvm_object) |llvm_object| { + llvm_object.updateNav(pt, nav_index) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + }; + } else if (comp.bin_file) |lf| { + lf.updateNav(pt, nav_index) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + error.CodegenFail => zcu.assertCodegenFailed(nav_index), + error.Overflow, error.RelocationNotByteAligned => { + switch (zcu.codegenFail(nav_index, "unable to codegen: {s}", .{@errorName(err)})) { + error.CodegenFail => return, + error.OutOfMemory => return diags.setAllocFailure(), + } + // Not a retryable failure. + }, + }; + } +} /// Provided by the CLI, processed into `LinkInput` instances at the start of /// the compilation pipeline. diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bb8faf583d..81376c45d8 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -224,21 +224,16 @@ pub fn createEmpty( else => 0x1000, }; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try allocPrint(arena, "{s}.obj", .{emit.sub_path}); - const coff = try arena.create(Coff); coff.* = .{ .base = .{ .tag = .coff, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.obj", .{fs.path.stem(emit.sub_path)}) + else + null, .stack_size = options.stack_size orelse 16777216, .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), .print_gc_sections = options.print_gc_sections, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 34e04ad557..498bc734c3 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -249,14 +249,6 @@ pub fn createEmpty( const is_dyn_lib = output_mode == .Lib and link_mode == .dynamic; const default_sym_version: elf.Versym = if (is_dyn_lib or comp.config.rdynamic) .GLOBAL else .LOCAL; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); - var rpath_table: std.StringArrayHashMapUnmanaged(void) = .empty; try rpath_table.entries.resize(arena, options.rpath_list.len); @memcpy(rpath_table.entries.items(.key), options.rpath_list); @@ -268,7 +260,10 @@ pub fn createEmpty( .tag = .elf, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)}) + else + null, .gc_sections = options.gc_sections orelse (optimize_mode != .Debug and output_mode != .Obj), .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 16777216, @@ -770,17 +765,13 @@ fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { const gpa = comp.gpa; const diags = &comp.link_diags; - const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{ - .root_dir = self.base.emit.root_dir, - .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname| - try fs.path.join(arena, &.{ dirname, path }) - else - path, + const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, raw); } else null; if (self.zigObjectPtr()) |zig_object| try zig_object.flush(self, tid); - if (module_obj_path) |path| openParseObjectReportingFailure(self, path); + if (zcu_obj_path) |path| openParseObjectReportingFailure(self, path); switch (comp.config.output_mode) { .Obj => return relocatable.flushObject(self, comp), diff --git a/src/link/Goff.zig b/src/link/Goff.zig index d0c2b8e80b..c222ae029f 100644 --- a/src/link/Goff.zig +++ b/src/link/Goff.zig @@ -41,7 +41,7 @@ pub fn createEmpty( .tag = .goff, .comp = comp, .emit = emit, - .zcu_object_sub_path = emit.sub_path, + .zcu_object_basename = emit.sub_path, .gc_sections = options.gc_sections orelse false, .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 0, diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 3b7b2b6740..dd50bd2a2f 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1,5 +1,4 @@ base: link.File, -disable_caching: bool, ofmt: union(enum) { elf: Elf, coff: Coff, @@ -231,7 +230,7 @@ pub fn createEmpty( .tag = .lld, .comp = comp, .emit = emit, - .zcu_object_sub_path = try allocPrint(arena, "{s}.{s}", .{ emit.sub_path, obj_file_ext }), + .zcu_object_basename = try allocPrint(arena, "{s}_zcu.{s}", .{ fs.path.stem(emit.sub_path), obj_file_ext }), .gc_sections = gc_sections, .print_gc_sections = options.print_gc_sections, .stack_size = stack_size, @@ -239,7 +238,6 @@ pub fn createEmpty( .file = null, .build_id = options.build_id, }, - .disable_caching = options.disable_lld_caching, .ofmt = switch (target.ofmt) { .coff => .{ .coff = try .init(comp, options) }, .elf => .{ .elf = try .init(comp, options) }, @@ -289,14 +287,11 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void { const full_out_path_z = try arena.dupeZ(u8, full_out_path); const opt_zcu = comp.zcu; - // 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. - const zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: { - const dirname = fs.path.dirname(full_out_path_z) orelse "."; - break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + const zcu_obj_path: ?Cache.Path = if (opt_zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; - log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"}); + log.debug("zcu_obj_path={?}", .{zcu_obj_path}); const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj) comp.compiler_rt_obj.?.full_object_path @@ -330,7 +325,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void { for (comp.win32_resource_table.keys()) |key| { object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path)); } - if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); + if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); @@ -368,14 +363,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_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. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); - } else { - break :p base.zcu_object_sub_path.?; - } + const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; const is_lib = comp.config.output_mode == .Lib; @@ -402,8 +391,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; - if (module_obj_path) |p| - break :blk Cache.Path.initCwd(p); + if (zcu_obj_path) |p| + break :blk p; // TODO I think this is unreachable. Audit this situation when solving the above TODO // regarding eliding redundant object -> object transformations. @@ -513,9 +502,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); - if (comp.implib_emit) |emit| { - const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + if (comp.emit_implib) |raw_emit_path| { + const path = try comp.resolveEmitPathFlush(arena, .temp, raw_emit_path); + try argv.append(try allocPrint(arena, "-IMPLIB:{}", .{path})); } if (comp.config.link_libc) { @@ -556,8 +545,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { try argv.append(key.status.success.res_path); } - if (module_obj_path) |p| { - try argv.append(p); + if (zcu_obj_path) |p| { + try argv.append(try p.toString(arena)); } if (coff.module_definition_file) |def| { @@ -808,14 +797,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_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. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); - } else { - break :p base.zcu_object_sub_path.?; - } + const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; const output_mode = comp.config.output_mode; @@ -862,8 +845,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; - if (module_obj_path) |p| - break :blk Cache.Path.initCwd(p); + if (zcu_obj_path) |p| + break :blk p; // TODO I think this is unreachable. Audit this situation when solving the above TODO // regarding eliding redundant object -> object transformations. @@ -1151,8 +1134,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { - try argv.append(p); + if (zcu_obj_path) |p| { + try argv.append(try p.toString(arena)); } if (comp.tsan_lib) |lib| { @@ -1387,14 +1370,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_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. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); - } else { - break :p base.zcu_object_sub_path.?; - } + const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; const is_obj = comp.config.output_mode == .Obj; @@ -1419,8 +1396,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; - if (module_obj_path) |p| - break :blk Cache.Path.initCwd(p); + if (zcu_obj_path) |p| + break :blk p; // TODO I think this is unreachable. Audit this situation when solving the above TODO // regarding eliding redundant object -> object transformations. @@ -1610,8 +1587,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { - try argv.append(p); + if (zcu_obj_path) |p| { + try argv.append(try p.toString(arena)); } if (compiler_rt_path) |p| { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8fd85df0a3..6c081653ea 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -173,13 +173,6 @@ pub fn createEmpty( const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); const allow_shlib_undefined = options.allow_shlib_undefined orelse false; const self = try arena.create(MachO); @@ -188,7 +181,10 @@ pub fn createEmpty( .tag = .macho, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)}) + else + null, .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 16777216, @@ -351,21 +347,16 @@ pub fn flush( const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); - const directory = self.base.emit.root_dir; - const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{ - .root_dir = directory, - .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname| - try fs.path.join(arena, &.{ dirname, path }) - else - path, + const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, raw); } else null; // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); if (self.getZigObject()) |zo| try zo.flush(self, tid); - if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path); - if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path); + if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path); + if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path); var positionals = std.ArrayList(link.Input).init(gpa); defer positionals.deinit(); @@ -387,7 +378,7 @@ pub fn flush( positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path)); } - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); + if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); if (comp.config.any_sanitize_thread) { try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path)); @@ -636,12 +627,9 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { const directory = self.base.emit.root_dir; const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); - const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, path }); - } else { - break :blk path; - } + const zcu_obj_path: ?[]const u8 = if (self.base.zcu_object_basename) |raw| p: { + const p = try comp.resolveEmitPathFlush(arena, .temp, raw); + break :p try p.toString(arena); } else null; var argv = std.ArrayList([]const u8).init(arena); @@ -670,7 +658,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { + if (zcu_obj_path) |p| { try argv.append(p); } } else { @@ -762,7 +750,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { + if (zcu_obj_path) |p| { try argv.append(p); } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 67e530b5cc..82293b9c45 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2951,21 +2951,16 @@ pub fn createEmpty( const output_mode = comp.config.output_mode; const wasi_exec_model = comp.config.wasi_exec_model; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); - const wasm = try arena.create(Wasm); wasm.* = .{ .base = .{ .tag = .wasm, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)}) + else + null, // Garbage collection is so crucial to WebAssembly that we design // the linker around the assumption that it will be on in the vast // majority of cases, and therefore express "no garbage collection" @@ -3834,15 +3829,9 @@ pub fn flush( if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items); - if (wasm.base.zcu_object_sub_path) |path| { - const module_obj_path: Path = .{ - .root_dir = wasm.base.emit.root_dir, - .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname| - try fs.path.join(arena, &.{ dirname, path }) - else - path, - }; - openParseObjectReportingFailure(wasm, module_obj_path); + if (wasm.base.zcu_object_basename) |raw| { + const zcu_obj_path: Path = try comp.resolveEmitPathFlush(arena, .temp, raw); + openParseObjectReportingFailure(wasm, zcu_obj_path); try prelink(wasm, prog_node); } diff --git a/src/link/Xcoff.zig b/src/link/Xcoff.zig index 97ea300ed2..93fda27f3f 100644 --- a/src/link/Xcoff.zig +++ b/src/link/Xcoff.zig @@ -41,7 +41,7 @@ pub fn createEmpty( .tag = .xcoff, .comp = comp, .emit = emit, - .zcu_object_sub_path = emit.sub_path, + .zcu_object_basename = emit.sub_path, .gc_sections = options.gc_sections orelse false, .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 0, diff --git a/src/main.zig b/src/main.zig index f7ad35d7cd..dc1d66381b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -699,55 +699,21 @@ const Emit = union(enum) { yes_default_path, yes: []const u8, - const Resolved = struct { - data: ?Compilation.EmitLoc, - dir: ?fs.Dir, - - fn deinit(self: *Resolved) void { - if (self.dir) |*dir| { - dir.close(); - } - } - }; - - fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: bool) !Resolved { - var resolved: Resolved = .{ .data = null, .dir = null }; - errdefer resolved.deinit(); - - switch (emit) { - .no => {}, - .yes_default_path => { - resolved.data = Compilation.EmitLoc{ - .directory = if (output_to_cache) null else .{ - .path = null, - .handle = fs.cwd(), - }, - .basename = default_basename, - }; - }, - .yes => |full_path| { - const basename = fs.path.basename(full_path); - if (fs.path.dirname(full_path)) |dirname| { - const handle = try fs.cwd().openDir(dirname, .{}); - resolved = .{ - .dir = handle, - .data = Compilation.EmitLoc{ - .basename = basename, - .directory = .{ - .path = dirname, - .handle = handle, - }, - }, - }; - } else { - resolved.data = Compilation.EmitLoc{ - .basename = basename, - .directory = .{ .path = null, .handle = fs.cwd() }, - }; + const OutputToCacheReason = enum { listen, @"zig run", @"zig test" }; + fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit { + return switch (emit) { + .no => .no, + .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename }, + .yes => |path| if (output_to_cache) |reason| { + switch (reason) { + .listen => fatal("--listen incompatible with explicit output path '{s}'", .{path}), + .@"zig run", .@"zig test" => fatal( + "'{s}' with explicit output path '{s}' requires explicit '-femit-bin=path' or '-fno-emit-bin'", + .{ @tagName(reason), path }, + ), } - }, - } - return resolved; + } else .{ .yes_path = path }, + }; } }; @@ -2830,7 +2796,7 @@ fn buildOutputType( .link => { create_module.opts.output_mode = if (is_shared_lib) .Lib else .Exe; if (emit_bin != .no) { - emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out; + emit_bin = if (out_path) |p| .{ .yes = p } else .yes_a_out; } if (emit_llvm) { fatal("-emit-llvm cannot be used when linking", .{}); @@ -3208,7 +3174,17 @@ fn buildOutputType( var cleanup_emit_bin_dir: ?fs.Dir = null; defer if (cleanup_emit_bin_dir) |*dir| dir.close(); - const output_to_cache = listen != .none; + // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if + // the binary is requested with no explicit path (as is the default), we emit to the cache. + const output_to_cache: ?Emit.OutputToCacheReason = switch (listen) { + .stdio, .ip4 => .listen, + .none => if (arg_mode == .run and emit_bin == .yes_default_path) + .@"zig run" + else if (arg_mode == .zig_test and emit_bin == .yes_default_path) + .@"zig test" + else + null, + }; const optional_version = if (have_version) version else null; const root_name = if (provided_name) |n| n else main_mod.fully_qualified_name; @@ -3225,150 +3201,48 @@ fn buildOutputType( }, }; - const a_out_basename = switch (target.ofmt) { - .coff => "a.exe", - else => "a.out", - }; - - const emit_bin_loc: ?Compilation.EmitLoc = switch (emit_bin) { - .no => null, - .yes_default_path => Compilation.EmitLoc{ - .directory = blk: { - switch (arg_mode) { - .run, .zig_test => break :blk null, - .build, .cc, .cpp, .translate_c, .zig_test_obj => { - if (output_to_cache) { - break :blk null; - } else { - break :blk .{ .path = null, .handle = fs.cwd() }; - } - }, - } - }, - .basename = if (clang_preprocessor_mode == .pch) - try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}) - else - try std.zig.binNameAlloc(arena, .{ + const emit_bin_resolved: Compilation.CreateOptions.Emit = switch (emit_bin) { + .no => .no, + .yes_default_path => emit: { + if (output_to_cache != null) break :emit .yes_cache; + const name = switch (clang_preprocessor_mode) { + .pch => try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}), + else => try std.zig.binNameAlloc(arena, .{ .root_name = root_name, .target = target, .output_mode = create_module.resolved_options.output_mode, .link_mode = create_module.resolved_options.link_mode, .version = optional_version, }), + }; + break :emit .{ .yes_path = name }; }, - .yes => |full_path| b: { - const basename = fs.path.basename(full_path); - if (fs.path.dirname(full_path)) |dirname| { - const handle = fs.cwd().openDir(dirname, .{}) catch |err| { - fatal("unable to open output directory '{s}': {s}", .{ dirname, @errorName(err) }); - }; - cleanup_emit_bin_dir = handle; - break :b Compilation.EmitLoc{ - .basename = basename, - .directory = .{ - .path = dirname, - .handle = handle, - }, - }; - } else { - break :b Compilation.EmitLoc{ - .basename = basename, - .directory = .{ .path = null, .handle = fs.cwd() }, - }; - } - }, - .yes_a_out => Compilation.EmitLoc{ - .directory = .{ .path = null, .handle = fs.cwd() }, - .basename = a_out_basename, + .yes => |path| if (output_to_cache != null) { + assert(output_to_cache == .listen); // there was an explicit bin path + fatal("--listen incompatible with explicit output path '{s}'", .{path}); + } else .{ .yes_path = path }, + .yes_a_out => emit: { + assert(output_to_cache == null); + break :emit .{ .yes_path = switch (target.ofmt) { + .coff => "a.exe", + else => "a.out", + } }; }, }; const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name}); - var emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache) catch |err| { - switch (emit_h) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_h_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_h_resolved.deinit(); + const emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache); const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name}); - var emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache) catch |err| { - switch (emit_asm) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_asm_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_asm_resolved.deinit(); + const emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache); const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name}); - var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache) catch |err| { - switch (emit_llvm_ir) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_llvm_ir_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_llvm_ir_resolved.deinit(); + const emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache); const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name}); - var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache) catch |err| { - switch (emit_llvm_bc) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_llvm_bc_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_llvm_bc_resolved.deinit(); + const emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache); - var emit_docs_resolved = emit_docs.resolve("docs", output_to_cache) catch |err| { - switch (emit_docs) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-docs', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory 'docs': {s}", .{@errorName(err)}); - }, - .no => unreachable, - } - }; - defer emit_docs_resolved.deinit(); + const emit_docs_resolved = emit_docs.resolve("docs", output_to_cache); const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) { .Obj => false, @@ -3378,7 +3252,7 @@ fn buildOutputType( // Note that cmake when targeting Windows will try to execute // zig cc to make an executable and output an implib too. const implib_eligible = is_exe_or_dyn_lib and - emit_bin_loc != null and target.os.tag == .windows; + emit_bin_resolved != .no and target.os.tag == .windows; if (!implib_eligible) { if (!emit_implib_arg_provided) { emit_implib = .no; @@ -3387,22 +3261,18 @@ fn buildOutputType( } } const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name}); - var emit_implib_resolved = switch (emit_implib) { - .no => Emit.Resolved{ .data = null, .dir = null }, - .yes => |p| emit_implib.resolve(default_implib_basename, output_to_cache) catch |err| { - fatal("unable to open directory from argument '-femit-implib', '{s}': {s}", .{ - p, @errorName(err), + const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) { + .no => .no, + .yes => emit_implib.resolve(default_implib_basename, output_to_cache), + .yes_default_path => emit: { + if (output_to_cache != null) break :emit .yes_cache; + const p = try fs.path.join(arena, &.{ + fs.path.dirname(emit_bin_resolved.yes_path) orelse ".", + default_implib_basename, }); - }, - .yes_default_path => Emit.Resolved{ - .data = Compilation.EmitLoc{ - .directory = emit_bin_loc.?.directory, - .basename = default_implib_basename, - }, - .dir = null, + break :emit .{ .yes_path = p }; }, }; - defer emit_implib_resolved.deinit(); var thread_pool: ThreadPool = undefined; try thread_pool.init(.{ @@ -3456,7 +3326,7 @@ fn buildOutputType( src.src_path = try dirs.local_cache.join(arena, &.{sub_path}); } - if (build_options.have_llvm and emit_asm != .no) { + if (build_options.have_llvm and emit_asm_resolved != .no) { // LLVM has no way to set this non-globally. const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" }; @import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv); @@ -3472,23 +3342,11 @@ fn buildOutputType( fatal("--debug-incremental requires -fincremental", .{}); } - const disable_lld_caching = !output_to_cache; - const cache_mode: Compilation.CacheMode = b: { + // Once incremental compilation is the default, we'll want some smarter logic here, + // considering things like the backend in use and whether there's a ZCU. + if (output_to_cache == null) break :b .none; if (incremental) break :b .incremental; - if (disable_lld_caching) break :b .incremental; - if (!create_module.resolved_options.have_zcu) break :b .whole; - - // TODO: once we support incremental compilation for the LLVM backend - // via saving the LLVM module into a bitcode file and restoring it, - // along with compiler state, this clause can be removed so that - // incremental cache mode is used for LLVM backend too. - if (create_module.resolved_options.use_llvm) break :b .whole; - - // Eventually, this default should be `.incremental`. However, since incremental - // compilation is currently an opt-in feature, it makes a strictly worse default cache mode - // than `.whole`. - // https://github.com/ziglang/zig/issues/21165 break :b .whole; }; @@ -3510,13 +3368,13 @@ fn buildOutputType( .main_mod = main_mod, .root_mod = root_mod, .std_mod = std_mod, - .emit_bin = emit_bin_loc, - .emit_h = emit_h_resolved.data, - .emit_asm = emit_asm_resolved.data, - .emit_llvm_ir = emit_llvm_ir_resolved.data, - .emit_llvm_bc = emit_llvm_bc_resolved.data, - .emit_docs = emit_docs_resolved.data, - .emit_implib = emit_implib_resolved.data, + .emit_bin = emit_bin_resolved, + .emit_h = emit_h_resolved, + .emit_asm = emit_asm_resolved, + .emit_llvm_ir = emit_llvm_ir_resolved, + .emit_llvm_bc = emit_llvm_bc_resolved, + .emit_docs = emit_docs_resolved, + .emit_implib = emit_implib_resolved, .lib_directories = create_module.lib_directories.items, .rpath_list = create_module.rpath_list.items, .symbol_wrap_set = symbol_wrap_set, @@ -3599,7 +3457,6 @@ fn buildOutputType( .test_filters = test_filters.items, .test_name_prefix = test_name_prefix, .test_runner_path = test_runner_path, - .disable_lld_caching = disable_lld_caching, .cache_mode = cache_mode, .subsystem = subsystem, .debug_compile_errors = debug_compile_errors, @@ -3744,13 +3601,8 @@ fn buildOutputType( }) { dev.checkAny(&.{ .run_command, .test_command }); - if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: { + if (test_exec_args.items.len == 0 and target.ofmt == .c and emit_bin_resolved != .no) { // Default to using `zig run` to execute the produced .c code from `zig test`. - const c_code_loc = emit_bin_loc orelse break :default_exec_args; - const c_code_directory = c_code_loc.directory orelse comp.bin_file.?.emit.root_dir; - const c_code_path = try fs.path.join(arena, &[_][]const u8{ - c_code_directory.path orelse ".", c_code_loc.basename, - }); try test_exec_args.appendSlice(arena, &.{ self_exe_path, "run" }); if (dirs.zig_lib.path) |p| { try test_exec_args.appendSlice(arena, &.{ "-I", p }); @@ -3775,7 +3627,7 @@ fn buildOutputType( if (create_module.dynamic_linker) |dl| { try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl }); } - try test_exec_args.append(arena, c_code_path); + try test_exec_args.append(arena, null); // placeholder for the path of the emitted C source file } try runOrTest( @@ -4354,12 +4206,22 @@ fn runOrTest( runtime_args_start: ?usize, link_libc: bool, ) !void { - const lf = comp.bin_file orelse return; - // A naive `directory.join` here will indeed get the correct path to the binary, - // however, in the case of cwd, we actually want `./foo` so that the path can be executed. - const exe_path = try fs.path.join(arena, &[_][]const u8{ - lf.emit.root_dir.path orelse ".", lf.emit.sub_path, - }); + const raw_emit_bin = comp.emit_bin orelse return; + const exe_path = switch (comp.cache_use) { + .none => p: { + if (fs.path.isAbsolute(raw_emit_bin)) break :p raw_emit_bin; + // Use `fs.path.join` to make a file in the cwd is still executed properly. + break :p try fs.path.join(arena, &.{ + ".", + raw_emit_bin, + }); + }, + .whole, .incremental => try comp.dirs.local_cache.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + raw_emit_bin, + }), + }; var argv = std.ArrayList([]const u8).init(gpa); defer argv.deinit(); @@ -5087,16 +4949,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { }; }; - const exe_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = "build", - .target = resolved_target.result, - .output_mode = .Exe, - }); - const emit_bin: Compilation.EmitLoc = .{ - .directory = null, // Use the local zig-cache. - .basename = exe_basename, - }; - process.raiseFileDescriptorLimit(); const cwd_path = try introspect.getResolvedCwd(arena); @@ -5357,8 +5209,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { .config = config, .root_mod = root_mod, .main_mod = build_mod, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .self_exe_path = self_exe_path, .thread_pool = &thread_pool, .verbose_cc = verbose_cc, @@ -5386,8 +5237,11 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { // Since incremental compilation isn't done yet, we use cache_mode = whole // above, and thus the output file is already closed. //try comp.makeBinFileExecutable(); - child_argv.items[argv_index_exe] = - try dirs.local_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?}); + child_argv.items[argv_index_exe] = try dirs.local_cache.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + comp.emit_bin.?, + }); } if (process.can_spawn) { @@ -5504,16 +5358,6 @@ fn jitCmd( .is_explicit_dynamic_linker = false, }; - const exe_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = options.cmd_name, - .target = resolved_target.result, - .output_mode = .Exe, - }); - const emit_bin: Compilation.EmitLoc = .{ - .directory = null, // Use the global zig-cache. - .basename = exe_basename, - }; - const self_exe_path = fs.selfExePathAlloc(arena) catch |err| { fatal("unable to find self exe path: {s}", .{@errorName(err)}); }; @@ -5605,8 +5449,7 @@ fn jitCmd( .config = config, .root_mod = root_mod, .main_mod = root_mod, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .self_exe_path = self_exe_path, .thread_pool = &thread_pool, .cache_mode = .whole, @@ -5637,7 +5480,11 @@ fn jitCmd( }; } - const exe_path = try dirs.global_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?}); + const exe_path = try dirs.global_cache.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + comp.emit_bin.?, + }); child_argv.appendAssumeCapacity(exe_path); } diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 6e69b93b96..6c048f7a87 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -314,7 +314,7 @@ const Eval = struct { const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len]; const result_dir = ".local-cache" ++ std.fs.path.sep_str ++ "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*); - const bin_name = try std.zig.binNameAlloc(arena, .{ + const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ .root_name = "root", // corresponds to the module name "root" .target = eval.target.resolved, .output_mode = .Exe,