diff options
Diffstat (limited to 'src/Compilation.zig')
| -rw-r--r-- | src/Compilation.zig | 1108 |
1 files changed, 611 insertions, 497 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 81d150b03f..065b717931 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -43,7 +43,6 @@ const Air = @import("Air.zig"); const Builtin = @import("Builtin.zig"); const LlvmObject = @import("codegen/llvm.zig").Object; const dev = @import("dev.zig"); -const ThreadSafeQueue = @import("ThreadSafeQueue.zig").ThreadSafeQueue; pub const Config = @import("Compilation/Config.zig"); @@ -56,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 @@ -68,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, @@ -113,17 +107,7 @@ win32_resource_table: if (dev.env.supports(.win32_resource)) std.AutoArrayHashMa } = .{}, link_diags: link.Diags, -link_task_queue: ThreadSafeQueue(link.Task) = .empty, -/// Ensure only 1 simultaneous call to `flushTaskQueue`. -link_task_queue_safety: std.debug.SafetyLock = .{}, -/// If any tasks are queued up that depend on prelink being finished, they are moved -/// here until prelink finishes. -link_task_queue_postponed: std.ArrayListUnmanaged(link.Task) = .empty, -/// Initialized with how many link input tasks are expected. After this reaches zero -/// the linker will begin the prelink phase. -/// Initialized in the Compilation main thread before the pipeline; modified only in -/// the linker task thread. -remaining_prelink_tasks: u32, +link_task_queue: link.Queue = .empty, /// Set of work that can be represented by only flags to determine whether the /// work is queued or not. @@ -270,12 +254,8 @@ 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, +link_prog_node: std.Progress.Node = std.Progress.Node.none, llvm_opt_bisect_limit: c_int, @@ -285,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, @@ -785,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); @@ -846,19 +844,34 @@ pub const RcIncludes = enum { }; const Job = union(enum) { - /// Corresponds to the task in `link.Task`. - /// Only needed for backends that haven't yet been updated to not race against Sema. - codegen_nav: InternPool.Nav.Index, - /// Corresponds to the task in `link.Task`. - /// Only needed for backends that haven't yet been updated to not race against Sema. - codegen_func: link.Task.CodegenFunc, - /// Corresponds to the task in `link.Task`. - /// Only needed for backends that haven't yet been updated to not race against Sema. - codegen_type: InternPool.Index, + /// Given the generated AIR for a function, put it onto the code generation queue. + /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that + /// all types are resolved before the linker task is queued. + /// If the backend does not support `Zcu.Feature.separate_thread`, codegen and linking happen immediately. + /// Before queueing this `Job`, increase the estimated total item count for both + /// `comp.zcu.?.codegen_prog_node` and `comp.link_prog_node`. + codegen_func: struct { + func: InternPool.Index, + /// The AIR emitted from analyzing `func`; owned by this `Job` in `gpa`. + air: Air, + }, + /// Queue a `link.ZcuTask` to emit this non-function `Nav` into the output binary. + /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that + /// all types are resolved before the linker task is queued. + /// If the backend does not support `Zcu.Feature.separate_thread`, the task is run immediately. + /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`. + link_nav: InternPool.Nav.Index, + /// Queue a `link.ZcuTask` to emit debug information for this container type. + /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that + /// all types are resolved before the linker task is queued. + /// If the backend does not support `Zcu.Feature.separate_thread`, the task is run immediately. + /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`. + link_type: InternPool.Index, + /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`. update_line_number: InternPool.TrackedInst.Index, /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed. /// This may be its first time being analyzed, or it may be outdated. - /// If the unit is a function, a `codegen_func` job will then be queued. + /// If the unit is a test function, an `analyze_func` job will then be queued. analyze_comptime_unit: InternPool.AnalUnit, /// This function must be semantically analyzed. /// This may be its first time being analyzed, or it may be outdated. @@ -1322,14 +1335,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); @@ -1369,15 +1374,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); @@ -1424,7 +1420,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 '<cache>/o/<hash>/'. 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 '<cache>/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 '<cache>/o/<hash>/'. 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, @@ -1433,22 +1460,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/<hash>' 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, @@ -1467,17 +1505,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(); }, } @@ -1504,28 +1541,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. @@ -1591,9 +1614,9 @@ pub const CreateOptions = struct { linker_tsaware: bool = false, linker_nxcompat: bool = false, linker_dynamicbase: bool = true, - linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null, + linker_compress_debug_sections: ?link.File.Lld.Elf.CompressDebugSections = null, linker_module_definition_file: ?[]const u8 = null, - linker_sort_section: ?link.File.Elf.SortSection = null, + linker_sort_section: ?link.File.Lld.Elf.SortSection = null, major_subsystem_version: ?u16 = null, minor_subsystem_version: ?u16 = null, clang_passthrough_mode: bool = false, @@ -1615,7 +1638,7 @@ pub const CreateOptions = struct { /// building such dependencies themselves, this flag must be set to avoid /// infinite recursion. skip_linker_dependencies: bool = false, - hash_style: link.File.Elf.HashStyle = .both, + hash_style: link.File.Lld.Elf.HashStyle = .both, entry: Entry = .default, force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .empty, stack_size: ?u64 = null, @@ -1663,6 +1686,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( @@ -1870,13 +1925,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; @@ -1926,7 +1986,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(); @@ -1939,18 +1999,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, @@ -2003,7 +2058,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), - .remaining_prelink_tasks = 0, + .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 @@ -2070,7 +2130,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, @@ -2089,6 +2148,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. @@ -2102,7 +2172,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. @@ -2137,49 +2207,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, }; @@ -2187,14 +2234,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }, } - // Handle the case of e.g. -fno-emit-bin -femit-llvm-ir. - if (options.emit_bin == null and (comp.verbose_llvm_ir != null or - comp.verbose_llvm_bc != null or - (use_llvm and comp.emit_asm != null) or - comp.emit_llvm_ir != null or - comp.emit_llvm_bc != null)) - { - if (opt_zcu) |zcu| zcu.llvm_object = try LlvmObject.create(arena, comp); + if (use_llvm) { + if (opt_zcu) |zcu| { + zcu.llvm_object = try LlvmObject.create(arena, comp); + } } break :comp comp; @@ -2216,7 +2259,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; comp.c_object_table.putAssumeCapacityNoClobber(c_object, {}); } - comp.remaining_prelink_tasks += @intCast(comp.c_object_table.count()); + comp.link_task_queue.pending_prelink_tasks += @intCast(comp.c_object_table.count()); // Add a `Win32Resource` for each `rc_source_files` and one for `manifest_file`. const win32_resource_count = @@ -2227,7 +2270,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // Add this after adding logic to updateWin32Resource to pass the // result into link.loadInput. loadInput integration is not implemented // for Windows linking logic yet. - //comp.remaining_prelink_tasks += @intCast(win32_resource_count); + //comp.link_task_queue.pending_prelink_tasks += @intCast(win32_resource_count); for (options.rc_source_files) |rc_source_file| { const win32_resource = try gpa.create(Win32Resource); errdefer gpa.destroy(win32_resource); @@ -2251,12 +2294,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. @@ -2278,78 +2316,76 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil const paths = try lci.resolveCrtPaths(arena, basenames, target); const fields = @typeInfo(@TypeOf(paths)).@"struct".fields; - try comp.link_task_queue.shared.ensureUnusedCapacity(gpa, fields.len + 1); + try comp.link_task_queue.queued_prelink.ensureUnusedCapacity(gpa, fields.len + 1); inline for (fields) |field| { if (@field(paths, field.name)) |path| { - comp.link_task_queue.shared.appendAssumeCapacity(.{ .load_object = path }); - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.queued_prelink.appendAssumeCapacity(.{ .load_object = path }); } } // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`. - comp.link_task_queue.shared.appendAssumeCapacity(.load_host_libc); - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.queued_prelink.appendAssumeCapacity(.load_host_libc); } else if (target.isMuslLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (musl.needsCrt0(comp.config.output_mode, comp.config.link_mode, comp.config.pie)) |f| { comp.queued_jobs.musl_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } switch (comp.config.link_mode) { .static => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_a)] = true, .dynamic => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_so)] = true, } - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (target.isGnuLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (glibc.needsCrt0(comp.config.output_mode)) |f| { comp.queued_jobs.glibc_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } comp.queued_jobs.glibc_shared_objects = true; - comp.remaining_prelink_tasks += glibc.sharedObjectsCount(&target); + comp.link_task_queue.pending_prelink_tasks += glibc.sharedObjectsCount(&target); comp.queued_jobs.glibc_crt_file[@intFromEnum(glibc.CrtFile.libc_nonshared_a)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (target.isFreeBSDLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (freebsd.needsCrt0(comp.config.output_mode)) |f| { comp.queued_jobs.freebsd_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } comp.queued_jobs.freebsd_shared_objects = true; - comp.remaining_prelink_tasks += freebsd.sharedObjectsCount(); + comp.link_task_queue.pending_prelink_tasks += freebsd.sharedObjectsCount(); } else if (target.isNetBSDLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (netbsd.needsCrt0(comp.config.output_mode)) |f| { comp.queued_jobs.netbsd_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } comp.queued_jobs.netbsd_shared_objects = true; - comp.remaining_prelink_tasks += netbsd.sharedObjectsCount(); + comp.link_task_queue.pending_prelink_tasks += netbsd.sharedObjectsCount(); } else if (target.isWasiLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; for (comp.wasi_emulated_libs) |crt_file| { comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(crt_file)] = true; } - comp.remaining_prelink_tasks += @intCast(comp.wasi_emulated_libs.len); + comp.link_task_queue.pending_prelink_tasks += @intCast(comp.wasi_emulated_libs.len); comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.execModelCrtFile(comp.config.wasi_exec_model))] = true; comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.CrtFile.libc_a)] = true; - comp.remaining_prelink_tasks += 2; + comp.link_task_queue.pending_prelink_tasks += 2; } else if (target.isMinGW()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; const main_crt_file: mingw.CrtFile = if (is_dyn_lib) .dllcrt2_o else .crt2_o; comp.queued_jobs.mingw_crt_file[@intFromEnum(main_crt_file)] = true; comp.queued_jobs.mingw_crt_file[@intFromEnum(mingw.CrtFile.libmingw32_lib)] = true; - comp.remaining_prelink_tasks += 2; + comp.link_task_queue.pending_prelink_tasks += 2; // When linking mingw-w64 there are some import libs we always need. try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len); @@ -2363,7 +2399,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil target.isMinGW()) { comp.queued_jobs.zigc_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } } @@ -2380,53 +2416,53 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } if (comp.wantBuildLibUnwindFromSource()) { comp.queued_jobs.libunwind = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) { comp.queued_jobs.libcxx = true; comp.queued_jobs.libcxxabi = true; - comp.remaining_prelink_tasks += 2; + comp.link_task_queue.pending_prelink_tasks += 2; } if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.any_sanitize_thread) { comp.queued_jobs.libtsan = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (can_build_compiler_rt) { if (comp.compiler_rt_strat == .lib) { log.debug("queuing a job to build compiler_rt_lib", .{}); comp.queued_jobs.compiler_rt_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (comp.compiler_rt_strat == .obj) { log.debug("queuing a job to build compiler_rt_obj", .{}); // In this case we are making a static library, so we ask // for a compiler-rt object to put in it. comp.queued_jobs.compiler_rt_obj = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (comp.ubsan_rt_strat == .lib) { log.debug("queuing a job to build ubsan_rt_lib", .{}); comp.queued_jobs.ubsan_rt_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (comp.ubsan_rt_strat == .obj) { log.debug("queuing a job to build ubsan_rt_obj", .{}); comp.queued_jobs.ubsan_rt_obj = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (is_exe_or_dyn_lib and comp.config.any_fuzz) { log.debug("queuing a job to build libfuzzer", .{}); comp.queued_jobs.fuzzer_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } } } - try comp.link_task_queue.shared.append(gpa, .load_explicitly_provided); - comp.remaining_prelink_tasks += 1; + try comp.link_task_queue.queued_prelink.append(gpa, .load_explicitly_provided); } - log.debug("total prelink tasks: {d}", .{comp.remaining_prelink_tasks}); + log.debug("queued prelink tasks: {d}", .{comp.link_task_queue.queued_prelink.items.len}); + log.debug("pending prelink tasks: {d}", .{comp.link_task_queue.pending_prelink_tasks}); return comp; } @@ -2434,6 +2470,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil pub fn destroy(comp: *Compilation) void { const gpa = comp.gpa; + // This needs to be destroyed first, because it might contain MIR which we only know + // how to interpret (which kind of MIR it is) from `comp.bin_file`. + comp.link_task_queue.deinit(comp); + if (comp.bin_file) |lf| lf.destroy(); if (comp.zcu) |zcu| zcu.deinit(); comp.cache_use.deinit(); @@ -2515,8 +2555,6 @@ pub fn destroy(comp: *Compilation) void { comp.failed_win32_resources.deinit(gpa); comp.link_diags.deinit(); - comp.link_task_queue.deinit(gpa); - comp.link_task_queue_postponed.deinit(gpa); comp.clearMiscFailures(); @@ -2550,8 +2588,28 @@ 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; + if (dev.env == .bootstrap) { + // zig1 uses `CacheMode.none`, but it doesn't need to know how to delete + // temporary directories; it doesn't have a real cache directory anyway. + return; + } + 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| { @@ -2562,10 +2620,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), + }); + }; } }, } @@ -2585,14 +2651,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. @@ -2639,10 +2718,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(); @@ -2651,52 +2728,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 @@ -2757,6 +2805,17 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } + // The linker progress node is set up here instead of in `performAllTheWork`, because + // we also want it around during `flush`. + const have_link_node = comp.bin_file != null; + if (have_link_node) { + comp.link_prog_node = main_progress_node.start("Linking", 0); + } + defer if (have_link_node) { + comp.link_prog_node.end(); + comp.link_prog_node = .none; + }; + try comp.performAllTheWork(main_progress_node); if (comp.zcu) |zcu| { @@ -2768,7 +2827,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { // The `test_functions` decl has been intentionally postponed until now, // at which point we must populate it with the list of test functions that // have been discovered and not filtered out. - try pt.populateTestFunctions(main_progress_node); + try pt.populateTestFunctions(); } try pt.processExports(); @@ -2795,11 +2854,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); + }, .whole => |whole| { if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); if (comp.parent_whole_cache) |pwc| { @@ -2811,18 +2877,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: { @@ -2847,6 +2901,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, @@ -2859,7 +2920,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 @@ -2867,10 +2927,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(), @@ -2881,10 +2940,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); // Calling `flush` may have produced errors, in which case the // cache manifest must not be written. @@ -2903,11 +2959,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); - }, } } @@ -2937,27 +2988,98 @@ 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 { + if (comp.zcu) |zcu| { + 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 sub_prog_node = comp.link_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 = 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, + .time_report = comp.time_report, + .sanitize_thread = comp.config.any_sanitize_thread, + .fuzz = comp.config.any_fuzz, + .lto = comp.config.lto, + }); + } + } if (comp.bin_file) |lf| { // This is needed before reading the error flags. - lf.flush(arena, tid, prog_node) catch |err| switch (err) { + lf.flush(arena, tid, comp.link_prog_node) catch |err| switch (err) { error.LinkFailure => {}, // Already reported. error.OutOfMemory => return error.OutOfMemory, }; } - if (comp.zcu) |zcu| { try link.File.C.flushEmitH(zcu); - - if (zcu.llvm_object) |llvm_object| { - try emitLlvmObject(comp, arena, default_artifact_directory, null, llvm_object, prog_node); - } } } @@ -3009,45 +3131,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 @@ -3068,7 +3151,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); @@ -3114,10 +3197,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; @@ -3195,82 +3274,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) }); - }; - } - } - } -} - -pub fn emitLlvmObject( +fn emitFromCObject( comp: *Compilation, arena: Allocator, - default_artifact_directory: Cache.Path, - bin_emit_loc: ?EmitLoc, - llvm_object: LlvmObject.Ptr, - prog_node: std.Progress.Node, -) !void { - 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, bin_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), - - .is_debug = comp.root_mod.optimize_mode == .Debug, - .is_small = comp.root_mod.optimize_mode == .ReleaseSmall, - .time_report = comp.time_report, - .sanitize_thread = comp.config.any_sanitize_thread, - .fuzz = comp.config.any_fuzz, - .lto = comp.config.lto, - }); -} + 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); -fn resolveEmitLoc( - 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; + 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 @@ -3512,7 +3548,7 @@ pub fn saveState(comp: *Compilation) !void { // TODO handle the union safety field //addBuf(&bufs, mem.sliceAsBytes(wasm.mir_instructions.items(.data))); addBuf(&bufs, mem.sliceAsBytes(wasm.mir_extra.items)); - addBuf(&bufs, mem.sliceAsBytes(wasm.all_zcu_locals.items)); + addBuf(&bufs, mem.sliceAsBytes(wasm.mir_locals.items)); addBuf(&bufs, mem.sliceAsBytes(wasm.tag_name_bytes.items)); addBuf(&bufs, mem.sliceAsBytes(wasm.tag_name_offs.items)); @@ -4156,28 +4192,15 @@ pub fn addWholeFileError( } } -pub fn performAllTheWork( +fn performAllTheWork( comp: *Compilation, main_progress_node: std.Progress.Node, ) JobError!void { - comp.work_queue_progress_node = main_progress_node; - defer comp.work_queue_progress_node = .none; - + // Regardless of errors, `comp.zcu` needs to update its generation number. defer if (comp.zcu) |zcu| { - zcu.sema_prog_node.end(); - zcu.sema_prog_node = .none; - zcu.codegen_prog_node.end(); - zcu.codegen_prog_node = .none; - zcu.generation += 1; }; - try comp.performAllTheWorkInner(main_progress_node); -} -fn performAllTheWorkInner( - comp: *Compilation, - main_progress_node: std.Progress.Node, -) JobError!void { // Here we queue up all the AstGen tasks first, followed by C object compilation. // We wait until the AstGen tasks are all completed before proceeding to the // (at least for now) single-threaded main work queue. However, C object compilation @@ -4189,11 +4212,13 @@ fn performAllTheWorkInner( comp.link_task_wait_group.reset(); defer comp.link_task_wait_group.wait(); - if (comp.link_task_queue.start()) { - comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); - } + comp.link_prog_node.increaseEstimatedTotalItems( + comp.link_task_queue.queued_prelink.items.len + // already queued prelink tasks + comp.link_task_queue.pending_prelink_tasks, // prelink tasks which will be queued + ); + 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 }); @@ -4471,7 +4496,7 @@ fn performAllTheWorkInner( }; } }, - .incremental => {}, + .none, .incremental => {}, } if (any_fatal_files or @@ -4499,15 +4524,31 @@ fn performAllTheWorkInner( } zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); - zcu.codegen_prog_node = if (comp.bin_file != null) main_progress_node.start("Code Generation", 0) else .none; + if (comp.bin_file != null) { + zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); + } + // We increment `pending_codegen_jobs` so that it doesn't reach 0 until after analysis finishes. + // That prevents the "Code Generation" node from constantly disappearing and reappearing when + // we're probably going to analyze more functions at some point. + assert(zcu.pending_codegen_jobs.swap(1, .monotonic) == 0); // don't let this become 0 until analysis finishes } + // When analysis ends, delete the progress nodes for "Semantic Analysis" and possibly "Code Generation". + defer if (comp.zcu) |zcu| { + zcu.sema_prog_node.end(); + zcu.sema_prog_node = .none; + if (zcu.pending_codegen_jobs.rmw(.Sub, 1, .monotonic) == 1) { + // Decremented to 0, so all done. + zcu.codegen_prog_node.end(); + zcu.codegen_prog_node = .none; + } + }; if (!comp.separateCodegenThreadOk()) { // Waits until all input files have been parsed. comp.link_task_wait_group.wait(); comp.link_task_wait_group.reset(); std.log.scoped(.link).debug("finished waiting for link_task_wait_group", .{}); - if (comp.remaining_prelink_tasks > 0) { + if (comp.link_task_queue.pending_prelink_tasks > 0) { // Indicates an error occurred preventing prelink phase from completing. return; } @@ -4552,26 +4593,91 @@ pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void { fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { switch (job) { - .codegen_nav => |nav_index| { + .codegen_func => |func| { + const zcu = comp.zcu.?; + const gpa = zcu.gpa; + var air = func.air; + errdefer { + zcu.codegen_prog_node.completeOne(); + comp.link_prog_node.completeOne(); + air.deinit(gpa); + } + if (!air.typesFullyResolved(zcu)) { + // Type resolution failed in a way which affects this function. This is a transitive + // failure, but it doesn't need recording, because this function semantically depends + // on the failed type, so when it is changed the function is updated. + zcu.codegen_prog_node.completeOne(); + comp.link_prog_node.completeOne(); + air.deinit(gpa); + return; + } + const shared_mir = try gpa.create(link.ZcuTask.LinkFunc.SharedMir); + shared_mir.* = .{ + .status = .init(.pending), + .value = undefined, + }; + assert(zcu.pending_codegen_jobs.rmw(.Add, 1, .monotonic) > 0); // the "Code Generation" node hasn't been ended + // This value is used as a heuristic to avoid queueing too much AIR/MIR at once (hence + // using a lot of memory). If this would cause too many AIR bytes to be in-flight, we + // will block on the `dispatchZcuLinkTask` call below. + const air_bytes: u32 = @intCast(air.instructions.len * 5 + air.extra.items.len * 4); + if (comp.separateCodegenThreadOk()) { + // `workerZcuCodegen` takes ownership of `air`. + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, workerZcuCodegen, .{ comp, func.func, air, shared_mir }); + comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ + .func = func.func, + .mir = shared_mir, + .air_bytes = air_bytes, + } }); + } else { + { + const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); + defer pt.deactivate(); + pt.runCodegen(func.func, &air, shared_mir); + } + assert(shared_mir.status.load(.monotonic) != .pending); + comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ + .func = func.func, + .mir = shared_mir, + .air_bytes = air_bytes, + } }); + air.deinit(gpa); + } + }, + .link_nav => |nav_index| { const zcu = comp.zcu.?; const nav = zcu.intern_pool.getNav(nav_index); if (nav.analysis != null) { const unit: InternPool.AnalUnit = .wrap(.{ .nav_val = nav_index }); if (zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit)) { + comp.link_prog_node.completeOne(); return; } } assert(nav.status == .fully_resolved); - comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index }); - }, - .codegen_func => |func| { - comp.dispatchCodegenTask(tid, .{ .codegen_func = func }); + if (!Air.valFullyResolved(zcu.navValue(nav_index), zcu)) { + // Type resolution failed in a way which affects this `Nav`. This is a transitive + // failure, but it doesn't need recording, because this `Nav` semantically depends + // on the failed type, so when it is changed the `Nav` will be updated. + comp.link_prog_node.completeOne(); + return; + } + comp.dispatchZcuLinkTask(tid, .{ .link_nav = nav_index }); }, - .codegen_type => |ty| { - comp.dispatchCodegenTask(tid, .{ .codegen_type = ty }); + .link_type => |ty| { + const zcu = comp.zcu.?; + if (zcu.failed_types.fetchSwapRemove(ty)) |*entry| entry.value.deinit(zcu.gpa); + if (!Air.typeFullyResolved(.fromInterned(ty), zcu)) { + // Type resolution failed in a way which affects this type. This is a transitive + // failure, but it doesn't need recording, because this type semantically depends + // on the failed type, so when that is changed, this type will be updated. + comp.link_prog_node.completeOne(); + return; + } + comp.dispatchZcuLinkTask(tid, .{ .link_type = ty }); }, .update_line_number => |ti| { - comp.dispatchCodegenTask(tid, .{ .update_line_number = ti }); + comp.dispatchZcuLinkTask(tid, .{ .update_line_number = ti }); }, .analyze_func => |func| { const named_frame = tracy.namedFrame("analyze_func"); @@ -4663,18 +4769,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { } } -/// The reason for the double-queue here is that the first queue ensures any -/// resolve_type_fully tasks are complete before this dispatch function is called. -fn dispatchCodegenTask(comp: *Compilation, tid: usize, link_task: link.Task) void { - if (comp.separateCodegenThreadOk()) { - comp.queueLinkTasks(&.{link_task}); - } else { - assert(comp.remaining_prelink_tasks == 0); - link.doTask(comp, tid, link_task); - } -} - -fn separateCodegenThreadOk(comp: *const Compilation) bool { +pub fn separateCodegenThreadOk(comp: *const Compilation) bool { if (InternPool.single_threaded) return false; const zcu = comp.zcu orelse return true; return zcu.backendSupportsFeature(.separate_thread); @@ -4694,12 +4789,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(); @@ -4718,8 +4813,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(); @@ -4869,11 +4964,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, @@ -4885,10 +4975,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, @@ -4903,27 +4990,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), }); }; @@ -5185,7 +5276,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(); @@ -5261,6 +5352,21 @@ pub const RtOptions = struct { allow_lto: bool = true, }; +fn workerZcuCodegen( + tid: usize, + comp: *Compilation, + func_index: InternPool.Index, + orig_air: Air, + out: *link.ZcuTask.LinkFunc.SharedMir, +) void { + var air = orig_air; + // We own `air` now, so we are responsbile for freeing it. + defer air.deinit(comp.gpa); + const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); + defer pt.deactivate(); + pt.runCodegen(func_index, &air, out); +} + fn buildRt( comp: *Compilation, root_source_name: []const u8, @@ -5515,9 +5621,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); @@ -5751,7 +5857,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 => {}, } } @@ -5792,7 +5898,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr }, }; - comp.queueLinkTasks(&.{.{ .load_object = c_object.status.success.object_path }}); + comp.queuePrelinkTasks(&.{.{ .load_object = c_object.status.success.object_path }}); } fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32_resource_prog_node: std.Progress.Node) !void { @@ -5995,7 +6101,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 => {}, } } } @@ -7167,12 +7273,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| .{ @@ -7185,7 +7285,7 @@ fn buildOutputFromZig( 3, // global cache is the same }, }, - .incremental => null, + .incremental, .none => null, }; const sub_compilation = try Compilation.create(gpa, arena, .{ @@ -7198,13 +7298,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, @@ -7225,7 +7321,7 @@ fn buildOutputFromZig( assert(out.* == null); out.* = crt_file; - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); } pub const CrtFileOptions = struct { @@ -7324,13 +7420,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, @@ -7349,7 +7441,7 @@ pub fn build_crt_file( try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); const crt_file = try sub_compilation.toCrtFile(); - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); { comp.mutex.lock(); @@ -7359,8 +7451,8 @@ pub fn build_crt_file( } } -pub fn queueLinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Compilation.Config) void { - comp.queueLinkTasks(switch (config.output_mode) { +pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Compilation.Config) void { + comp.queuePrelinkTasks(switch (config.output_mode) { .Exe => unreachable, .Obj => &.{.{ .load_object = path }}, .Lib => &.{switch (config.link_mode) { @@ -7372,19 +7464,41 @@ pub fn queueLinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Co /// Only valid to call during `update`. Automatically handles queuing up a /// linker worker task if there is not already one. -pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.Task) void { - if (comp.link_task_queue.enqueue(comp.gpa, tasks) catch |err| switch (err) { +pub fn queuePrelinkTasks(comp: *Compilation, tasks: []const link.PrelinkTask) void { + comp.link_task_queue.enqueuePrelink(comp, tasks) catch |err| switch (err) { error.OutOfMemory => return comp.setAllocFailure(), - }) { - comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); + }; +} + +/// The reason for the double-queue here is that the first queue ensures any +/// resolve_type_fully tasks are complete before this dispatch function is called. +fn dispatchZcuLinkTask(comp: *Compilation, tid: usize, task: link.ZcuTask) void { + if (!comp.separateCodegenThreadOk()) { + assert(tid == 0); + if (task == .link_func) { + assert(task.link_func.mir.status.load(.monotonic) != .pending); + } + link.doZcuTask(comp, tid, task); + task.deinit(comp.zcu.?); + return; } + comp.link_task_queue.enqueueZcu(comp, task) catch |err| switch (err) { + error.OutOfMemory => { + task.deinit(comp.zcu.?); + comp.setAllocFailure(); + }, + }; } 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(), }; |
