diff --git a/src/Compilation.zig b/src/Compilation.zig index cae0acc1ffafd1231f9cfd451041d5c0ae536498..94dad2b74fa251bafaa9dd8246d52d8e45fdf0e1 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -20,7 +20,6 @@ const link = @import("link.zig"); const tracy = @import("tracy.zig"); const trace = tracy.trace; const build_options = @import("build_options"); -const debugLogScopeEnabled = @import("main.zig").debugLogScopeEnabled; const LibCInstallation = std.zig.LibCInstallation; const glibc = @import("libs/glibc.zig"); const musl = @import("libs/musl.zig"); @@ -148,6 +147,7 @@ failed_win32_resources: if (dev.env.supports(.win32_resource)) std.array_hash_ma pub fn values(_: @This()) [0]void { return .{}; } + pub fn clearRetainingCapacity(_: @This()) void {} pub fn deinit(_: @This(), _: Allocator) void {} } = .{}, @@ -238,8 +238,6 @@ reference_trace: ?u32 = null, /// This mutex guards all `Compilation` mutable state. mutex: std.Io.Mutex = .init, -test_filters: []const []const u8, - link_prog_node: std.Progress.Node = .none, llvm_opt_bisect_limit: c_int, @@ -1635,28 +1633,11 @@ pub const CreateOptions = struct { }; }; -fn addModuleTableToCacheHash(zcu: *Zcu, hash: *Cache.HashHelper) error{ OutOfMemory, Unexpected }!void { - assert(zcu.module_roots.count() != 0); // module_roots is populated - - for (zcu.module_roots.keys(), zcu.module_roots.values()) |mod, opt_mod_root_file| { - if (mod == zcu.std_mod) continue; // redundant - if (opt_mod_root_file.unwrap()) |mod_root_file| { - if (zcu.fileByIndex(mod_root_file).is_builtin) continue; // redundant - } - cache_helpers.addModule(hash, mod); - hash.add(mod.root.root); - hash.addBytes(mod.root.sub_path); - hash.addBytes(mod.root_src_path); - hash.addListOfBytes(mod.deps.keys()); - } -} - const RtStrat = enum { none, lib, obj, zcu }; pub const CreateDiagnostic = union(enum) { export_table_import_table_conflict, emit_h_without_zcu, - illegal_zig_import, cross_libc_unavailable, find_native_libc: std.zig.LibCInstallation.FindError, libc_installation_missing_cc_dir, @@ -1672,7 +1653,6 @@ pub const CreateDiagnostic = union(enum) { switch (diag) { .export_table_import_table_conflict => try w.writeAll("'--import-table' and '--export-table' cannot be used together"), .emit_h_without_zcu => try w.writeAll("cannot emit C header with no Zig source files"), - .illegal_zig_import => try w.writeAll("this compiler implementation does not support importing the root source file of a provided module"), .cross_libc_unavailable => try w.writeAll("unable to provide libc for this target"), .find_native_libc => |err| try w.print("failed to find libc installation: {t}", .{err}), .libc_installation_missing_cc_dir => try w.writeAll("libc installation is missing cc directory"), @@ -1720,7 +1700,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, const use_llvm = options.config.use_llvm; const target = &options.root_mod.resolved_target.result; - const comp: *Compilation = comp: { + const comp = comp: { // We put the `Compilation` itself in the arena. Freeing the arena will free the module. // It's initialized later after we prepare the initialization options. const root_name = try arena.dupeSentinel(u8, options.root_name, 0); @@ -1988,7 +1968,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, // TODO audit this and make sure everything is in it const comp = try arena.create(Compilation); - const opt_zcu: ?*Zcu = if (have_zcu) blk: { + const opt_zcu = if (have_zcu) blk: { // Pre-open the directory handles for cached ZIR code so that it does not need // to redundantly happen for each AstGen operation. const zir_sub_dir = "z"; @@ -2041,19 +2021,95 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, zcu.* = .{ .gpa = gpa, .comp = comp, - .main_mod = main_mod, + .llvm_object = null, + .root_mod = options.root_mod, + .main_mod = main_mod, .std_mod = std_mod, + .sema_prog_node = .none, + .codegen_prog_node = .none, + .pending_codegen_jobs = .init(0), + + .cur_sema_prog_node = .none, + .global_zir_cache = global_zir_cache, .local_zir_cache = local_zir_cache, + + .all_exports = .empty, + .free_exports = .empty, + .single_exports = .empty, + .multi_exports = .empty, + + .module_roots = .empty, + .builtin_modules = .empty, + + .import_table = .empty, + .alive_files = .empty, + .embed_table = .empty, + + .intern_pool = .empty, + + .compile_logs = .empty, + .compile_log_lines = .empty, + .free_compile_log_lines = .empty, + + .illegal_module_root = null, + .multi_module_err = null, + .failed_imports = .empty, + .failed_files = .empty, + .skip_analysis_this_update = false, + .analysis_in_progress = .empty, + .failed_analysis = .empty, + .transitive_failed_analysis = .empty, + .dependency_loops = .empty, + .dependency_loop_nodes = .empty, + .failed_codegen = .empty, + .failed_types = .empty, + .failed_exports = .empty, + .error_limit = error_limit, - .llvm_object = null, + + .outdated_lock = if (std.debug.runtime_safety) .init, + .potentially_outdated = .empty, + .outdated = .empty, + .outdated_ready = .{ + .funcs = .empty, + .other = .empty, + }, + .retryable_failures = .empty, + .analysis_roots_buffer = undefined, .analysis_roots_len = 0, + .resolved_references = null, + + .test_filters = options.test_filters, + .test_functions = .empty, + + .global_assembly = .empty, + + .reference_table = .empty, + .all_references = .empty, + .free_references = .empty, + + .inline_reference_frames = .empty, + .free_inline_reference_frames = .empty, + + .type_reference_table = .empty, + .all_type_references = .empty, + .free_type_references = .empty, + + .std_lang_decl_values = .initFill(.none), + + .incremental_debug_state = if (build_options.enable_debug_extensions) .init, + + .cur_analysis_timer = null, + .codegen_task_pool = try .init(arena), + + .generation = 0, + .anon_name_counter = 0, }; - try zcu.init(gpa, io, options.thread_limit); break :blk zcu; } else blk: { if (options.emit_h != .no) return diag.fail(.emit_h_without_zcu); @@ -2091,7 +2147,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .reference_trace = options.reference_trace, .time_report = if (options.time_report) .init else null, .stack_report = options.stack_report, - .test_filters = options.test_filters, .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs, .debug_compile_errors = options.debug_compile_errors, .debug_incremental = options.debug_incremental, @@ -2143,18 +2198,8 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, comp.config.any_sanitize_c = any_sanitize_c; comp.config.any_fuzz = any_fuzz; - if (opt_zcu) |zcu| { - // Finish initializing the `zcu` after the fields on `comp` have been initialized. - zcu.initAfterCompilation(); - - // Populate `zcu.module_roots`. - const active = zcu.acquire(); - defer active.release(); - active.pt.populateModuleRootTable() catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.IllegalZigImport => return diag.fail(.illegal_zig_import), - }; - } + // Initialize the `zcu` after the fields on `comp` have been initialized. + if (opt_zcu) |zcu| zcu.init(); const lf_open_opts: link.File.OpenOptions = .{ .linker_script = options.linker_script, @@ -2250,20 +2295,10 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, hash.add(options.config.is_test); hash.addListOfBytes(options.test_filters); hash.add(options.skip_linker_dependencies); - hash.add(options.emit_h != .no); + //hash.add(options.emit_h != .no); hash.add(error_limit); - // Here we put the root source file path name, but *not* with addFile. - // We want the hash to be the same regardless of the contents of the - // source file, because incremental compilation will handle it, but we - // do want to namespace different source file names because they are - // likely different compilations and therefore this would be likely to - // cause cache hits. - if (comp.zcu) |zcu| { - try addModuleTableToCacheHash(zcu, &hash); - } else { - cache_helpers.addModule(&hash, options.root_mod); - } + try comp.addModuleTableToCacheHash(&hash); // In the case of incremental cache mode, this `artifact_directory` // is computed based on a hash of non-linker inputs, and it is where all @@ -2315,22 +2350,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, comp.cache_use = .{ .whole = whole }; }, } - - if (use_llvm and - (comp.emit_bin != null or - comp.emit_asm != null or - comp.emit_llvm_ir != null or - comp.emit_llvm_bc != null or - log_llvm_ir: { - break :log_llvm_ir debugLogScopeEnabled("llvm-ir") or debugLogScopeEnabled("opt-llvm-ir"); - })) - { - if (opt_zcu) |zcu| { - dev.check(.llvm_backend); - zcu.llvm_object = try LlvmObject.create(arena, zcu); - } - } - break :comp comp; }; errdefer comp.destroy(); @@ -2599,8 +2618,8 @@ pub fn destroy(comp: *Compilation) void { } comp.win32_resources.deinit(gpa); - for (comp.failed_win32_resources.values()) |*value| { - value.deinit(gpa); + for (comp.failed_win32_resources.values()) |*bundle| { + bundle.deinit(gpa); } comp.failed_win32_resources.deinit(gpa); @@ -2624,8 +2643,8 @@ pub fn clearMiscFailures(comp: *Compilation) void { comp.misc_failures = .{}; } -pub fn getTarget(self: *const Compilation) *const Target { - return &self.root_mod.resolved_target.result; +pub fn getTarget(comp: *const Compilation) *const Target { + return &comp.root_mod.resolved_target.result; } /// Only legal to call when cache mode is incremental and a link file is present. @@ -2644,6 +2663,18 @@ pub fn hotCodeSwap( fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { const io = comp.io; + if (comp.bin_file) |lf| { + lf.endProgress(); + comp.link_prog_node.end(); + comp.link_prog_node = .none; + switch (comp.cache_use) { + .none, .whole => { + lf.destroy(); + comp.bin_file = null; + }, + .incremental => {}, + } + } switch (comp.cache_use) { .none => |none| { if (none.tmp_artifact_directory) |*tmp_dir| { @@ -2660,17 +2691,14 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { // replicated in the output binary (the output instead contains a reference to that // file which debug tooling can look through). So, in that particular case, we need // to keep this directory around so that the output binary can be debugged. - if (comp.bin_file != null and comp.getTarget().ofmt == .macho and comp.config.debug_format != .strip) { - // We are emitting an unstripped Mach-O binary with the LLVM backend: the ZCU - // object file must remain on-disk for its debug info. - return; + if (comp.bin_file == null or comp.getTarget().ofmt != .macho or comp.config.debug_format == .strip) { + const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| { + log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{ + comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err, + }); + }; } - const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); - comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| { - log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{ - comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err, - }); - }; } }, .incremental => return, @@ -2679,10 +2707,6 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { man.deinit(); whole.cache_manifest = null; } - if (comp.bin_file) |lf| { - lf.destroy(); - comp.bin_file = null; - } if (whole.tmp_artifact_directory) |*tmp_dir| { tmp_dir.handle.close(io); whole.tmp_artifact_directory = null; @@ -2695,6 +2719,13 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { } }, } + assert(!comp.config.incremental); // always uses incremental cache mode + if (comp.zcu) |zcu| { + if (zcu.llvm_object) |llvm_object| { + llvm_object.deinit(); + zcu.llvm_object = null; + } + } } pub const UpdateError = error{ @@ -2716,11 +2747,24 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - comp.clearMiscFailures(); - comp.last_update_was_cache_hit = false; - if (comp.time_report) |*tr| { - tr.deinit(gpa); // this is information about an old update - tr.* = .init; + reset: { + comp.clearMiscFailures(); + comp.last_update_was_cache_hit = false; + if (comp.time_report) |*tr| { + tr.deinit(comp.gpa); + tr.* = .init; + } + if (comp.file_system_inputs) |file_system_inputs| file_system_inputs.clearRetainingCapacity(); + + if (comp.zcu) |zcu| { + zcu.skip_analysis_this_update = false; + if (zcu.generation > 0) break :reset; + try zcu.reset(arena); + } + for (comp.failed_c_objects.values()) |bundle| bundle.destroy(comp.gpa); + comp.failed_c_objects.clearRetainingCapacity(); + for (comp.failed_win32_resources.values()) |*bundle| bundle.deinit(comp.gpa); + comp.failed_win32_resources.clearRetainingCapacity(); } var tmp_dir_rand_int: u64 = undefined; @@ -2865,39 +2909,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE if (comp.zcu) |zcu| { assert(zcu.cur_analysis_timer == null); - zcu.skip_analysis_this_update = false; - // TODO: doing this in `resolveReferences` later could avoid adding inputs for dead embedfiles. Investigate! for (zcu.embed_table.keys()) |embed_file| { try comp.appendFileSystemInput(embed_file.path); } - - zcu.analysis_roots_len = 0; - - zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.std_mod; - zcu.analysis_roots_len += 1; - - // Normally we rely on importing std to in turn import the root source file in the start code. - // However, the main module is distinct from the root module in tests, so that won't happen there. - if (comp.config.is_test and zcu.main_mod != zcu.std_mod) { - zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.main_mod; - zcu.analysis_roots_len += 1; - } - - if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { - zcu.analysis_roots_buffer[zcu.analysis_roots_len] = compiler_rt_mod; - zcu.analysis_roots_len += 1; - } - - if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { - zcu.analysis_roots_buffer[zcu.analysis_roots_len] = ubsan_rt_mod; - zcu.analysis_roots_len += 1; - } - - if (zcu.root_mod.deps.get("zigc")) |zigc_mod| { - zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zigc_mod; - zcu.analysis_roots_len += 1; - } } // The linker progress node is set up here instead of in `performAllTheWork`, because @@ -2919,11 +2934,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE comp.link_prog_node = main_progress_node.start("Linking", initial_estimated_total); lf.startProgress(comp.link_prog_node); } - defer if (comp.bin_file) |lf| { - lf.endProgress(); - comp.link_prog_node.end(); - comp.link_prog_node = .none; - }; try comp.performAllTheWork(main_progress_node, arena); @@ -2969,7 +2979,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE comp.setMiscFailure(.link_depfile, "writing linker dependency file failed: {t}", .{err}); }; - if (anyErrors(comp)) { + if (comp.anyErrors()) { // Skip flushing and keep source files loaded for error reporting. return; } @@ -2983,7 +2993,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE } switch (comp.cache_use) { - .none, .incremental => try flush(comp, arena), + .none, .incremental => try comp.flush(arena), .whole => |whole| { if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); if (comp.parent_whole_cache) |pwc| { @@ -3064,11 +3074,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE }; } - try flush(comp, arena); + try comp.flush(arena); // Calling `flush` may have produced errors, in which case the // cache manifest must not be written. - if (anyErrors(comp)) return; + if (comp.anyErrors()) return; // Release the lock and close the file before writing the manifest. // This avoids the potential of another process getting a cache hit and @@ -3168,7 +3178,6 @@ fn flush(comp: *Compilation, arena: Allocator) (Io.Cancelable || Allocator.Error defer tid.release(io); 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); @@ -3303,23 +3312,42 @@ fn renameTmpIntoCache( /// anything from the link cache manifest. pub const link_hash_implementation_version = 14; -fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void { - comptime assert(link_hash_implementation_version == 14); +fn addModuleTableToCacheHash(comp: *Compilation, hash: *Cache.HashHelper) Allocator.Error!void { + const gpa = comp.gpa; + var mods: std.array_hash_map.Auto(*Module, void) = .empty; + defer mods.deinit(gpa); + + try mods.putNoClobber(gpa, comp.root_mod, {}); + if (comp.zcu) |zcu| try mods.put(gpa, zcu.main_mod, {}); + + var mod_index: usize = 0; + while (mod_index < mods.count()) : (mod_index += 1) { + const mod = mods.keys()[mod_index]; - if (comp.zcu) |zcu| { // No need to hash the actual file contents here because it is // redundant with the logic in `PerThread.update` which iterates over // `zcu.alive_files` and adds those files discovered via `@import` to // the whole cache manifest. - try addModuleTableToCacheHash(zcu, &man.hash); + cache_helpers.addModule(hash, mod); + hash.add(mod.root.root); + hash.addBytes(mod.root.sub_path); + hash.addBytes(mod.root_src_path); + hash.addListOfBytes(mod.deps.keys()); + for (mod.deps.values()) |dep_mod| try mods.put(gpa, dep_mod, {}); + } +} + +fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void { + comptime assert(link_hash_implementation_version == 14); + + try comp.addModuleTableToCacheHash(&man.hash); + + if (comp.zcu) |zcu| { // Synchronize with other matching comments: ZigOnlyHashStuff - man.hash.addListOfBytes(comp.test_filters); - man.hash.add(comp.skip_linker_dependencies); + man.hash.addListOfBytes(zcu.test_filters); //man.hash.add(zcu.emit_h != .no); man.hash.add(zcu.error_limit); - } else { - cache_helpers.addModule(&man.hash, comp.root_mod); } try link.hashInputs(man, comp.link_inputs); @@ -3788,7 +3816,7 @@ fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void { } /// This function is temporally single-threaded. -pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { +pub fn getAllErrorsAlloc(comp: *Compilation) Allocator.Error!ErrorBundle { const gpa = comp.gpa; const io = comp.io; @@ -3831,6 +3859,15 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { } if (comp.zcu) |zcu| zcu_errors: { + if (zcu.illegal_module_root) |mod| { + try bundle.addRootErrorMessage(.{ + .msg = try bundle.printString( + "this compiler implementation does not support using '{f}/{s}' as the root of module '{s}'", + .{ mod.root.fmt(comp), mod.root_src_path, mod.fully_qualified_name }, + ), + .notes_len = 0, + }); + } if (zcu.multi_module_err != null) { try zcu.addFileInMultipleModulesError(&bundle); break :zcu_errors; @@ -4359,7 +4396,9 @@ fn performAllTheWork( defer if (comp.zcu) |zcu| zcu.codegen_task_pool.cancel(zcu); if (comp.zcu) |zcu| { // Regardless of errors, `comp.zcu` needs to update its generation number. - defer zcu.generation += 1; + defer if (comp.config.incremental) { + zcu.generation += 1; + }; const active = zcu.acquire(); defer active.release(); try active.pt.update(main_progress_node, &decl_work_timer); diff --git a/src/Zcu.zig b/src/Zcu.zig index 01b136b01514721616febf43f65827b36d0ced4f..fdae0d840a69e6bc19ebddb32788b1598fef9865 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -40,6 +40,7 @@ const AnalUnit = InternPool.AnalUnit; const BuiltinFn = std.zig.BuiltinFn; const codegen = @import("codegen.zig"); const LlvmObject = @import("codegen/llvm.zig").Object; +const debugLogScopeEnabled = @import("main.zig").debugLogScopeEnabled; const dev = @import("dev.zig"); const Zoir = std.zig.Zoir; const ZonGen = std.zig.ZonGen; @@ -69,18 +70,18 @@ root_mod: *Module, /// `root_mod` is the test runner, and `main_mod` is the user's source file which has the tests. main_mod: *Module, std_mod: *Module, -sema_prog_node: std.Progress.Node = .none, -codegen_prog_node: std.Progress.Node = .none, +sema_prog_node: std.Progress.Node, +codegen_prog_node: std.Progress.Node, /// The number of codegen jobs which are pending or in-progress. Whichever thread drops this value /// to 0 is responsible for ending `codegen_prog_node`. While semantic analysis is happening, this /// value bottoms out at 1 instead of 0, to ensure that it can only drop to 0 after analysis is /// completed (since semantic analysis could trigger more codegen work). -pending_codegen_jobs: std.atomic.Value(u32) = .init(0), +pending_codegen_jobs: std.atomic.Value(u32), /// This is the progress node *under* `sema_prog_node` which is currently running. /// When we have to pause to analyze something else, we just temporarily rename this node. /// Eventually, when we thread semantic analysis, we will want one of these per thread. -cur_sema_prog_node: std.Progress.Node = .none, +cur_sema_prog_node: std.Progress.Node, /// Used by AstGen worker to load and store ZIR cache. global_zir_cache: Cache.Directory, @@ -89,27 +90,26 @@ local_zir_cache: Cache.Directory, /// This is where all `Export` values are stored. Not all values here are necessarily valid exports; /// to enumerate all exports, `single_exports` and `multi_exports` must be consulted. -all_exports: std.ArrayList(Export) = .empty, +all_exports: std.ArrayList(Export), /// This is a list of free indices in `all_exports`. These indices may be reused by exports from /// future semantic analysis. -free_exports: std.ArrayList(Export.Index) = .empty, +free_exports: std.ArrayList(Export.Index), /// Maps from an `AnalUnit` which performs a single export, to the index into `all_exports` of /// the export it performs. Note that the key is not the `Decl` being exported, but the `AnalUnit` /// whose analysis triggered the export. -single_exports: std.array_hash_map.Auto(AnalUnit, Export.Index) = .empty, +single_exports: std.array_hash_map.Auto(AnalUnit, Export.Index), /// Like `single_exports`, but for `AnalUnit`s which perform multiple exports. /// The exports are `all_exports.items[index..][0..len]`. multi_exports: std.array_hash_map.Auto(AnalUnit, extern struct { index: u32, len: u32, -}) = .empty, - -/// Key is the digest returned by `Builtin.hash`; value is the corresponding module. -builtin_modules: std.array_hash_map.Auto(Cache.BinDigest, *Module) = .empty, +}), /// Populated as soon as the `Compilation` is created. Guaranteed to contain all modules, even builtin ones. /// Modules whose root file is not a Zig or ZON file have the value `.none`. -module_roots: std.array_hash_map.Auto(*Module, File.Index.Optional) = .empty, +module_roots: std.array_hash_map.Auto(*Module, File.Index.Optional), +/// Key is the digest returned by `Builtin.hash`; value is the corresponding module. +builtin_modules: std.array_hash_map.Auto(Cache.BinDigest, *Module), /// The set of all the Zig source files in the Zig Compilation Unit. Tracked in /// order to iterate over it and check which source files have been modified on @@ -132,26 +132,14 @@ import_table: std.array_hash_map.Custom( pub const eql = @compileError("all accesses should be through ImportTableAdapter"); }, true, // This is necessary! Without it, the map tries to use its Context to rehash. #21918 -) = .empty, - +), /// The set of all files in `import_table` which are "alive" this update, meaning /// they are reachable by traversing imports starting from an analysis root. This /// is usually all files in `import_table`, but some could be omitted if an incremental /// update removes an import, or if a module specified on the CLI is never imported. /// Reconstructed on every update, after AstGen and before Sema. /// Value is why the file is alive. -alive_files: std.array_hash_map.Auto(File.Index, File.Reference) = .empty, - -/// If this is populated, a "file exists in multiple modules" error should be emitted. -/// This causes file errors to not be shown, because we don't really know which files -/// should be alive (because the user has messed up their imports somewhere!). -/// Cleared and recomputed every update, after AstGen and before Sema. -multi_module_err: ?struct { - file: File.Index, - modules: [2]*Module, - refs: [2]File.Reference, -} = null, - +alive_files: std.array_hash_map.Auto(File.Index, File.Reference), /// The set of all the files which have been loaded with `@embedFile` in the Module. /// We keep track of this in order to iterate over it and check which files have been /// modified on the file system when an update is requested, as well as to cache @@ -162,49 +150,19 @@ multi_module_err: ?struct { /// /// This table owns all of the `*EmbedFile` memory, which is allocated into gpa. embed_table: std.array_hash_map.Custom( - *EmbedFile, + EmbedFile, void, struct { pub const hash = @compileError("all accesses should be through EmbedTableAdapter"); pub const eql = @compileError("all accesses should be through EmbedTableAdapter"); }, true, // This is necessary! Without it, the map tries to use its Context to rehash. #21918 -) = .empty, +), /// Stores all Type and Value objects. /// The idea is that this will be periodically garbage-collected, but such logic /// is not yet implemented. -intern_pool: InternPool = .empty, - -/// Value explains why this `AnalUnit` is being analyzed. It is `null` for the topmost analysis -/// (index 0), and non-`null` for all others. -analysis_in_progress: std.array_hash_map.Auto(AnalUnit, ?*const DependencyReason) = .empty, -/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. -failed_analysis: std.array_hash_map.Auto(AnalUnit, *ErrorMsg) = .empty, -/// This `AnalUnit` failed semantic analysis because it required analysis of another `AnalUnit` which itself failed. -transitive_failed_analysis: std.array_hash_map.Auto( - AnalUnit, - if (build_options.enable_debug_extensions) TransitiveFailureReason else void, -) = .empty, -/// This `Nav` succeeded analysis, but failed codegen. -/// This may be a simple "value" `Nav`, or it may be a function. -/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. -/// While multiple threads are active (most of the time!), this is guarded by `zcu.comp.mutex`, as -/// codegen and linking run on a separate thread. -failed_codegen: std.array_hash_map.Auto(InternPool.Nav.Index, *ErrorMsg) = .empty, -failed_types: std.array_hash_map.Auto(InternPool.Index, *ErrorMsg) = .empty, - -/// Key is an `AnalUnit` which is in `dependency_loop_nodes`. For each dependency loop, exactly one -/// unit in the loop is in this map, though the choice is arbitrary and not necessarily reproducible -/// between compilations. So, instead of (for instance) defining where the dependency loop "starts", -/// this map simply exists to allow easily iterating all dependency loops exactly once. -dependency_loops: std.array_hash_map.Auto(AnalUnit, void) = .empty, -/// Key is an `AnalUnit`, value is the `AnalUnit` which the key references and why it does so. -/// All units in here form loops. To iterate loops, see `dependency_loops`. -dependency_loop_nodes: std.array_hash_map.Auto(AnalUnit, struct { - unit: AnalUnit, - reason: DependencyReason, -}) = .empty, +intern_pool: InternPool, /// Keep track of `@compileLog`s per `AnalUnit`. /// We track the source location of the first `@compileLog` call, and all logged lines as a linked list. @@ -220,17 +178,20 @@ compile_logs: std.array_hash_map.Auto(AnalUnit, struct { .offset = LazySrcLoc.Offset.nodeOffset(self.node_offset), }; } -}) = .empty, -compile_log_lines: std.ArrayList(CompileLogLine) = .empty, -free_compile_log_lines: std.ArrayList(CompileLogLine.Index) = .empty, -/// This tracks files which triggered errors when generating AST/ZIR/ZOIR. -/// If not `null`, the value is a retryable error (the file status is guaranteed -/// to be `.retryable_failure`). Otherwise, the file status is `.astgen_failure` -/// or `.success`, and there are ZIR/ZOIR errors which should be printed. -/// We just store a `[]u8` instead of a full `*ErrorMsg`, because the source -/// location is always the entire file. The `[]u8` memory is owned by the map -/// and allocated into `gpa`. -failed_files: std.array_hash_map.Auto(File.Index, ?[]u8) = .empty, +}), +compile_log_lines: std.ArrayList(CompileLogLine), +free_compile_log_lines: std.ArrayList(CompileLogLine.Index), + +illegal_module_root: ?*Module, +/// If this is populated, a "file exists in multiple modules" error should be emitted. +/// This causes file errors to not be shown, because we don't really know which files +/// should be alive (because the user has messed up their imports somewhere!). +/// Cleared and recomputed every update, after AstGen and before Sema. +multi_module_err: ?struct { + file: File.Index, + modules: [2]*Module, + refs: [2]File.Reference, +}, /// AstGen is not aware of modules, and so cannot determine whether an import /// string makes sense. That is the job of a traversal after AstGen. /// @@ -257,11 +218,48 @@ failed_imports: std.ArrayList(struct { import_string: Zir.NullTerminatedString, import_token: Ast.TokenIndex, kind: enum { file_outside_module_root, illegal_zig_import }, -}) = .empty, -failed_exports: std.array_hash_map.Auto(Export.Index, *ErrorMsg) = .empty, -/// If analysis failed due to a cimport error, the corresponding Clang errors -/// are stored here. -cimport_errors: std.array_hash_map.Auto(AnalUnit, std.zig.ErrorBundle) = .empty, +}), +/// This tracks files which triggered errors when generating AST/ZIR/ZOIR. +/// If not `null`, the value is a retryable error (the file status is guaranteed +/// to be `.retryable_failure`). Otherwise, the file status is `.astgen_failure` +/// or `.success`, and there are ZIR/ZOIR errors which should be printed. +/// We just store a `[]u8` instead of a full `*ErrorMsg`, because the source +/// location is always the entire file. The `[]u8` memory is owned by the map +/// and allocated into `gpa`. +failed_files: std.array_hash_map.Auto(File.Index, ?[]u8), +/// If `true`, then semantic analysis must not occur on this update due to AstGen errors. +/// Essentially the entire pipeline after AstGen, including Sema, codegen, and link, is skipped. +/// Reset to `false` at the start of each update in `Compilation.update`. +skip_analysis_this_update: bool, +/// Value explains why this `AnalUnit` is being analyzed. It is `null` for the topmost analysis +/// (index 0), and non-`null` for all others. +analysis_in_progress: std.array_hash_map.Auto(AnalUnit, ?*const DependencyReason), +/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. +failed_analysis: std.array_hash_map.Auto(AnalUnit, *ErrorMsg), +/// This `AnalUnit` failed semantic analysis because it required analysis of another `AnalUnit` which itself failed. +transitive_failed_analysis: std.array_hash_map.Auto( + AnalUnit, + if (build_options.enable_debug_extensions) TransitiveFailureReason else void, +), +/// Key is an `AnalUnit` which is in `dependency_loop_nodes`. For each dependency loop, exactly one +/// unit in the loop is in this map, though the choice is arbitrary and not necessarily reproducible +/// between compilations. So, instead of (for instance) defining where the dependency loop "starts", +/// this map simply exists to allow easily iterating all dependency loops exactly once. +dependency_loops: std.array_hash_map.Auto(AnalUnit, void), +/// Key is an `AnalUnit`, value is the `AnalUnit` which the key references and why it does so. +/// All units in here form loops. To iterate loops, see `dependency_loops`. +dependency_loop_nodes: std.array_hash_map.Auto(AnalUnit, struct { + unit: AnalUnit, + reason: DependencyReason, +}), +/// This `Nav` succeeded analysis, but failed codegen. +/// This may be a simple "value" `Nav`, or it may be a function. +/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. +/// While multiple threads are active (most of the time!), this is guarded by `zcu.comp.mutex`, as +/// codegen and linking run on a separate thread. +failed_codegen: std.array_hash_map.Auto(InternPool.Nav.Index, *ErrorMsg), +failed_types: std.array_hash_map.Auto(InternPool.Index, *ErrorMsg), +failed_exports: std.array_hash_map.Auto(Export.Index, *ErrorMsg), /// Maximum amount of distinct error values, set by --error-limit error_limit: ErrorInt, @@ -269,14 +267,14 @@ error_limit: ErrorInt, /// In safe builds, `Type.assertHasLayout` may be called cross-thread, so this lock /// guards accesses to `outdated` and `potentially_outdated`. In unsafe builds, the /// lock is not needed and is compiled out. -outdated_lock: if (std.debug.runtime_safety) std.Io.RwLock else void = if (std.debug.runtime_safety) .init, +outdated_lock: if (std.debug.runtime_safety) std.Io.RwLock else void, /// Value is the number of PO dependencies of this AnalUnit. /// This value will decrease as we perform semantic analysis to learn what is outdated. /// If any of these PO deps is outdated, this value will be moved to `outdated`. -potentially_outdated: std.array_hash_map.Auto(AnalUnit, u32) = .empty, +potentially_outdated: std.array_hash_map.Auto(AnalUnit, u32), /// Value is the number of PO dependencies of this AnalUnit. /// Once this value drops to 0, the AnalUnit is a candidate for re-analysis. -outdated: std.array_hash_map.Auto(AnalUnit, u32) = .empty, +outdated: std.array_hash_map.Auto(AnalUnit, u32), /// This is the set of all `AnalUnit`s in `outdated` whose PO dependency count is 0. /// Such `AnalUnit`s are ready for immediate re-analysis. /// See `findOutdatedToAnalyze` for details. @@ -286,69 +284,64 @@ outdated_ready: struct { funcs: std.array_hash_map.Auto(InternPool.Index, void), /// Does not contain `.func` units. other: std.array_hash_map.Auto(AnalUnit, void), -} = .{ .funcs = .empty, .other = .empty }, +}, /// This contains a list of AnalUnit whose analysis or codegen failed, but the /// failure was something like running out of disk space, and trying again may /// succeed. On the next update, we will flush this list, marking all members of /// it as outdated. -retryable_failures: std.ArrayList(AnalUnit) = .empty, +retryable_failures: std.ArrayList(AnalUnit), /// These are the modules which we initially queue for analysis in `Compilation.update`. /// `resolveReferences` will use these as the root of its reachability traversal. analysis_roots_buffer: [5]*Module, -analysis_roots_len: usize = 0, +analysis_roots_len: usize, /// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and /// reset to `null` when any semantic analysis occurs (since this invalidates the data). /// Allocated into `gpa`. -resolved_references: ?std.array_hash_map.Auto(AnalUnit, ?ResolvedReference) = null, +resolved_references: ?std.array_hash_map.Auto(AnalUnit, ?ResolvedReference), -/// If `true`, then semantic analysis must not occur on this update due to AstGen errors. -/// Essentially the entire pipeline after AstGen, including Sema, codegen, and link, is skipped. -/// Reset to `false` at the start of each update in `Compilation.update`. -skip_analysis_this_update: bool = false, +test_filters: []const []const u8, +test_functions: std.array_hash_map.Auto(InternPool.Nav.Index, void), -test_functions: std.array_hash_map.Auto(InternPool.Nav.Index, void) = .empty, - -global_assembly: std.array_hash_map.Auto(AnalUnit, []u8) = .empty, +global_assembly: std.array_hash_map.Auto(AnalUnit, []u8), /// Key is the `AnalUnit` *performing* the reference. This representation allows /// incremental updates to quickly delete references caused by a specific `AnalUnit`. /// Value is index into `all_references` of the first reference triggered by the unit. /// The `next` field on the `Reference` forms a linked list of all references /// triggered by the key `AnalUnit`. -reference_table: std.array_hash_map.Auto(AnalUnit, u32) = .empty, -all_references: std.ArrayList(Reference) = .empty, +reference_table: std.array_hash_map.Auto(AnalUnit, u32), +all_references: std.ArrayList(Reference), /// Freelist of indices in `all_references`. -free_references: std.ArrayList(u32) = .empty, +free_references: std.ArrayList(u32), -inline_reference_frames: std.ArrayList(InlineReferenceFrame) = .empty, -free_inline_reference_frames: std.ArrayList(InlineReferenceFrame.Index) = .empty, +inline_reference_frames: std.ArrayList(InlineReferenceFrame), +free_inline_reference_frames: std.ArrayList(InlineReferenceFrame.Index), /// Key is the `AnalUnit` *performing* the reference. This representation allows /// incremental updates to quickly delete references caused by a specific `AnalUnit`. /// Value is index into `all_type_reference` of the first reference triggered by the unit. /// The `next` field on the `TypeReference` forms a linked list of all type references /// triggered by the key `AnalUnit`. -type_reference_table: std.array_hash_map.Auto(AnalUnit, u32) = .empty, -all_type_references: std.ArrayList(TypeReference) = .empty, +type_reference_table: std.array_hash_map.Auto(AnalUnit, u32), +all_type_references: std.ArrayList(TypeReference), /// Freelist of indices in `all_type_references`. -free_type_references: std.ArrayList(u32) = .empty, +free_type_references: std.ArrayList(u32), /// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the element. -std_lang_decl_values: StdLangDecl.Memoized = .initFill(.none), +std_lang_decl_values: StdLangDecl.Memoized, -incremental_debug_state: if (build_options.enable_debug_extensions) IncrementalDebugState else void = - if (build_options.enable_debug_extensions) .init else {}, +incremental_debug_state: if (build_options.enable_debug_extensions) IncrementalDebugState else void, /// Times semantic analysis of the current `AnalUnit`. When we pause to analyze a different unit, /// this timer must be temporarily paused and resumed later. -cur_analysis_timer: ?Compilation.Timer = null, +cur_analysis_timer: ?Compilation.Timer, codegen_task_pool: CodegenTaskPool, -generation: u32 = 0, +generation: u32, -/// Only access from the Sema thread. +/// Only accessed from the Sema thread. anon_name_counter: u32, pub const DependencyReason = struct { @@ -434,7 +427,7 @@ pub const EmbedTableAdapter = struct { _ = ctx; return @truncate(std.hash.Wyhash.hash(@backingInt(path.root), path.sub_path)); } - pub fn eql(ctx: EmbedTableAdapter, a_path: Compilation.Path, b_file: *EmbedFile, b_index: usize) bool { + pub fn eql(ctx: EmbedTableAdapter, a_path: Compilation.Path, b_file: EmbedFile, b_index: usize) bool { _ = ctx; _ = b_index; const b_path = b_file.path; @@ -1250,10 +1243,15 @@ pub const EmbedFile = struct { pub const Index = enum(u32) { _, - pub fn get(idx: Index, zcu: *const Zcu) *EmbedFile { - return zcu.embed_table.keys()[@backingInt(idx)]; + pub fn get(idx: Index, zcu: *const Zcu) *const EmbedFile { + return &zcu.embed_table.keys()[@backingInt(idx)]; } }; + + pub fn deinit(ef: *EmbedFile, gpa: Allocator) void { + ef.path.deinit(gpa); + ef.* = undefined; + } }; /// This struct holds data necessary to construct API-facing `AllErrors.Message`. @@ -2894,16 +2892,130 @@ pub const CompileError = error{ ComptimeBreak, }; -pub fn init(zcu: *Zcu, gpa: Allocator, io: Io, thread_count: usize) !void { - try zcu.intern_pool.init(gpa, io, thread_count); -} - /// It is valid to not call this function before `deinit` in error paths. /// Requires the fields on `zcu.comp` to already be initialized. -pub fn initAfterCompilation(zcu: *Zcu) void { +pub fn init(zcu: *Zcu) void { zcu.initTracyPlots(); } +pub fn reset(zcu: *Zcu, arena: Allocator) Allocator.Error!void { + const comp = zcu.comp; + const gpa = comp.gpa; + const io = comp.io; + + zcu.all_exports.clearRetainingCapacity(); + zcu.free_exports.clearRetainingCapacity(); + zcu.single_exports.clearRetainingCapacity(); + zcu.multi_exports.clearRetainingCapacity(); + + zcu.module_roots.clearRetainingCapacity(); + // builtin modules can still be reused from a previous non-incremental update + + for (zcu.import_table.keys()) |file| zcu.destroyFile(file); + zcu.import_table.clearRetainingCapacity(); + zcu.alive_files.clearRetainingCapacity(); + for (zcu.embed_table.keys()) |*ef| ef.deinit(gpa); + zcu.embed_table.clearRetainingCapacity(); + + zcu.compile_logs.clearRetainingCapacity(); + zcu.compile_log_lines.clearRetainingCapacity(); + zcu.free_compile_log_lines.clearRetainingCapacity(); + + zcu.illegal_module_root = null; + zcu.multi_module_err = null; + zcu.failed_imports.clearRetainingCapacity(); + for (zcu.failed_files.values()) |maybe_msg| if (maybe_msg) |msg| gpa.free(msg); + zcu.failed_files.clearRetainingCapacity(); + zcu.analysis_in_progress.clearRetainingCapacity(); + for (zcu.failed_analysis.values()) |msg| msg.destroy(gpa); + zcu.failed_analysis.clearRetainingCapacity(); + zcu.transitive_failed_analysis.clearRetainingCapacity(); + zcu.dependency_loops.clearRetainingCapacity(); + zcu.dependency_loop_nodes.clearRetainingCapacity(); + for (zcu.failed_codegen.values()) |msg| msg.destroy(gpa); + zcu.failed_codegen.clearRetainingCapacity(); + for (zcu.failed_types.values()) |msg| msg.destroy(gpa); + zcu.failed_types.clearRetainingCapacity(); + for (zcu.failed_exports.values()) |msg| msg.destroy(gpa); + zcu.failed_exports.clearRetainingCapacity(); + + assert(zcu.potentially_outdated.count() == 0); + assert(zcu.outdated.count() == 0); + assert(zcu.outdated_ready.funcs.count() == 0); + assert(zcu.outdated_ready.other.count() == 0); + zcu.retryable_failures.clearRetainingCapacity(); + + zcu.analysis_roots_len = 0; + zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.std_mod; + zcu.analysis_roots_len += 1; + // Normally we rely on importing std to in turn import the root source file in the start code. + // However, the main module is distinct from the root module in tests, so that won't happen there. + if (comp.config.is_test and zcu.main_mod != zcu.std_mod) { + zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.main_mod; + zcu.analysis_roots_len += 1; + } + if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { + zcu.analysis_roots_buffer[zcu.analysis_roots_len] = compiler_rt_mod; + zcu.analysis_roots_len += 1; + } + if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { + zcu.analysis_roots_buffer[zcu.analysis_roots_len] = ubsan_rt_mod; + zcu.analysis_roots_len += 1; + } + if (zcu.root_mod.deps.get("zigc")) |zigc_mod| { + zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zigc_mod; + zcu.analysis_roots_len += 1; + } + zcu.clearCachedResolvedReferences(); + + zcu.test_functions.clearRetainingCapacity(); + + zcu.global_assembly.clearRetainingCapacity(); + + zcu.reference_table.clearRetainingCapacity(); + zcu.all_references.clearRetainingCapacity(); + zcu.free_references.clearRetainingCapacity(); + + zcu.inline_reference_frames.clearRetainingCapacity(); + zcu.free_inline_reference_frames.clearRetainingCapacity(); + + zcu.type_reference_table.clearRetainingCapacity(); + zcu.all_type_references.clearRetainingCapacity(); + zcu.free_type_references.clearRetainingCapacity(); + + zcu.std_lang_decl_values = .initFill(.none); + + for (zcu.codegen_task_pool.task_funcs) |task_func| assert(task_func == .none); + + assert(zcu.generation == 0); + + zcu.anon_name_counter = 0; + + zcu.intern_pool.deinit(gpa, io); + zcu.intern_pool = .empty; + try zcu.intern_pool.init(gpa, io, comp.thread_limit); + + { + const active = zcu.acquire(); + defer active.release(); + try active.pt.populateModuleRootTable(); + } + + assert(zcu.llvm_object == null); + if (comp.config.use_llvm and + (comp.emit_bin != null or + comp.emit_asm != null or + comp.emit_llvm_ir != null or + comp.emit_llvm_bc != null or + log_llvm_ir: { + break :log_llvm_ir debugLogScopeEnabled("llvm-ir") or debugLogScopeEnabled("opt-llvm-ir"); + })) + { + dev.check(.llvm_backend); + zcu.llvm_object = try .create(if (comp.config.incremental) comp.arena else arena, zcu); + } +} + pub fn deinit(zcu: *Zcu) void { const comp = zcu.comp; const io = comp.io; @@ -2911,70 +3023,54 @@ pub fn deinit(zcu: *Zcu) void { { if (zcu.llvm_object) |llvm_object| llvm_object.deinit(); - zcu.builtin_modules.deinit(gpa); + zcu.global_zir_cache.handle.close(io); + zcu.local_zir_cache.handle.close(io); + + zcu.all_exports.deinit(gpa); + zcu.free_exports.deinit(gpa); + zcu.single_exports.deinit(gpa); + zcu.multi_exports.deinit(gpa); + zcu.module_roots.deinit(gpa); - for (zcu.import_table.keys()) |file_index| { - zcu.destroyFile(file_index); - } + zcu.builtin_modules.deinit(gpa); + + for (zcu.import_table.keys()) |file| zcu.destroyFile(file); zcu.import_table.deinit(gpa); zcu.alive_files.deinit(gpa); - - for (zcu.embed_table.keys()) |embed_file| { - embed_file.path.deinit(gpa); - gpa.destroy(embed_file); - } + for (zcu.embed_table.keys()) |*ef| ef.deinit(gpa); zcu.embed_table.deinit(gpa); - zcu.local_zir_cache.handle.close(io); - zcu.global_zir_cache.handle.close(io); + zcu.compile_logs.deinit(gpa); + zcu.compile_log_lines.deinit(gpa); + zcu.free_compile_log_lines.deinit(gpa); - for (zcu.failed_analysis.values()) |value| value.destroy(gpa); - for (zcu.failed_codegen.values()) |value| value.destroy(gpa); - for (zcu.failed_types.values()) |value| value.destroy(gpa); + zcu.failed_imports.deinit(gpa); + for (zcu.failed_files.values()) |maybe_msg| if (maybe_msg) |msg| gpa.free(msg); + zcu.failed_files.deinit(gpa); zcu.analysis_in_progress.deinit(gpa); + for (zcu.failed_analysis.values()) |msg| msg.destroy(gpa); zcu.failed_analysis.deinit(gpa); zcu.transitive_failed_analysis.deinit(gpa); zcu.dependency_loops.deinit(gpa); zcu.dependency_loop_nodes.deinit(gpa); + for (zcu.failed_codegen.values()) |msg| msg.destroy(gpa); zcu.failed_codegen.deinit(gpa); + for (zcu.failed_types.values()) |msg| msg.destroy(gpa); zcu.failed_types.deinit(gpa); - - for (zcu.failed_files.values()) |value| { - if (value) |msg| gpa.free(msg); - } - zcu.failed_files.deinit(gpa); - zcu.failed_imports.deinit(gpa); - - for (zcu.failed_exports.values()) |value| { - value.destroy(gpa); - } + for (zcu.failed_exports.values()) |msg| msg.destroy(gpa); zcu.failed_exports.deinit(gpa); - for (zcu.cimport_errors.values()) |*errs| { - errs.deinit(gpa); - } - zcu.cimport_errors.deinit(gpa); - - zcu.compile_logs.deinit(gpa); - zcu.compile_log_lines.deinit(gpa); - zcu.free_compile_log_lines.deinit(gpa); - - zcu.all_exports.deinit(gpa); - zcu.free_exports.deinit(gpa); - zcu.single_exports.deinit(gpa); - zcu.multi_exports.deinit(gpa); - zcu.potentially_outdated.deinit(gpa); zcu.outdated.deinit(gpa); zcu.outdated_ready.funcs.deinit(gpa); zcu.outdated_ready.other.deinit(gpa); zcu.retryable_failures.deinit(gpa); + if (zcu.resolved_references) |*r| r.deinit(gpa); + zcu.test_functions.deinit(gpa); - for (zcu.global_assembly.values()) |s| { - gpa.free(s); - } + for (zcu.global_assembly.values()) |s| gpa.free(s); zcu.global_assembly.deinit(gpa); zcu.reference_table.deinit(gpa); @@ -2988,8 +3084,6 @@ pub fn deinit(zcu: *Zcu) void { zcu.all_type_references.deinit(gpa); zcu.free_type_references.deinit(gpa); - if (zcu.resolved_references) |*r| r.deinit(gpa); - if (comp.debugIncremental()) { zcu.incremental_debug_state.deinit(gpa); } @@ -4352,8 +4446,8 @@ fn resolveReferencesInner(zcu: *Zcu) Allocator.Error!std.array_hash_map.Auto(Ana .unnamed_test => true, .@"test", .decltest => a: { const fqn_slice = nav.fqn.toSlice(ip); - if (comp.test_filters.len > 0) { - for (comp.test_filters) |test_filter| { + if (zcu.test_filters.len > 0) { + for (zcu.test_filters) |test_filter| { if (std.mem.find(u8, fqn_slice, test_filter) != null) break; } else break :a false; } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index bb09f50cf02be848121d63c01e679232a6324ad7..8c789491b5b3121c3ddebd99451ff0f169d5b157 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -148,6 +148,12 @@ pub fn update( comp.link_queue.finishZcuQueue(comp); } + if (zcu.illegal_module_root) |_| { + // We cannot run AstGen because we failed to set up the module roots. We can return early + // because this is a guaranteed compile error. + return; + } + { const tracy_trace = traceNamed(@src(), "astgen"); defer tracy_trace.end(); @@ -203,11 +209,9 @@ pub fn update( // On the other hand, it's fine to directly iterate `zcu.embed_table.keys()` here // because `workerUpdateEmbedFile` can't invalidate it. The different here is that one // `@embedFile` can't trigger analysis of a new `@embedFile`! - for (0.., zcu.embed_table.keys()) |ef_index_usize, ef| { + for (0.., zcu.embed_table.keys()) |ef_index_usize, *ef| { const ef_index: Zcu.EmbedFile.Index = @fromBackingInt(@intCast(ef_index_usize)); - astgen_group.async(io, workerUpdateEmbedFile, .{ - comp, ef_index, ef, - }); + astgen_group.async(io, workerUpdateEmbedFile, .{ comp, ef_index, ef }); } try astgen_group.await(io); @@ -219,8 +223,8 @@ pub fn update( // they belong to. const any_fatal_files = try pt.computeAliveFiles(); - // If the cache mode is `whole`, add every alive source file to the manifest. switch (comp.cache_use) { + .none => {}, .whole => |whole| if (whole.cache_manifest) |man| { for (zcu.alive_files.keys()) |file_index| { const file = zcu.fileByIndex(file_index); @@ -250,7 +254,11 @@ pub fn update( }; } }, - .none, .incremental => {}, + .incremental => { + // Do not include file contents in the cache manifest because we + // want the hash to be the same regardless of the contents of the + // source files so that incremental can do its thing. + }, } if (comp.time_report) |*tr| { @@ -2494,11 +2502,7 @@ pub fn doImport( } /// This is called once during `Compilation.create` and never again. "builtin" modules don't yet /// exist, so are not added to `module_roots` here. They must be added when they are created. -pub fn populateModuleRootTable(pt: Zcu.PerThread) error{ - OutOfMemory, - /// One of the specified modules had its root source file at an illegal path. - IllegalZigImport, -}!void { +pub fn populateModuleRootTable(pt: Zcu.PerThread) Allocator.Error!void { const zcu = pt.zcu; const comp = zcu.comp; const gpa = comp.gpa; @@ -2507,7 +2511,7 @@ pub fn populateModuleRootTable(pt: Zcu.PerThread) error{ // We'll initially add [mod, undefined] pairs, and when we reach the pair while // iterating, rewrite the undefined value. const roots = &zcu.module_roots; - roots.clearRetainingCapacity(); + assert(roots.count() == 0); // Start with: // * `std_mod`, which is the main root of analysis @@ -2542,7 +2546,9 @@ pub fn populateModuleRootTable(pt: Zcu.PerThread) error{ errdefer path.deinit(gpa); if (try path.isIllegalZigImport(gpa, zcu.comp.dirs)) { - return error.IllegalZigImport; + // One of the specified modules had its root source file at an illegal path. + zcu.illegal_module_root = mod; + return; } const gop = try zcu.import_table.getOrPutAdapted(gpa, path, Zcu.ImportTableAdapter{ .zcu = zcu }); @@ -2781,15 +2787,21 @@ fn updateBuiltinModule(pt: Zcu.PerThread, opts: Builtin) Allocator.Error!struct const gpa = comp.gpa; const io = comp.io; - const gop = try zcu.builtin_modules.getOrPut(gpa, opts.hash()); - if (gop.found_existing) return .{ // the `File` is up-to-date - .file = zcu.module_roots.get(gop.value_ptr.*).?.unwrap().?, - .module_root = gop.value_ptr.*, + const mod: *Module = mod: { + const gop = try zcu.builtin_modules.getOrPut(gpa, opts.hash()); + if (!gop.found_existing) { + errdefer _ = zcu.builtin_modules.pop(); + gop.value_ptr.* = try .createBuiltin(comp.arena, opts, comp.dirs); + } + const mod = gop.value_ptr.*; + assert(std.mem.eql(u8, &mod.getBuiltinOptions(comp.config).hash(), gop.key_ptr)); // builtin is its own builtin + break :mod mod; }; - errdefer _ = zcu.builtin_modules.pop(); - const mod: *Module = try .createBuiltin(comp.arena, opts, comp.dirs); - assert(std.mem.eql(u8, &mod.getBuiltinOptions(comp.config).hash(), gop.key_ptr)); // builtin is its own builtin + if (zcu.module_roots.get(mod)) |file| { + // We have already loaded this builtin.zig before! + return .{ .file = file.unwrap().?, .module_root = mod }; + } const path = try mod.root.join(gpa, comp.dirs, "builtin.zig"); errdefer path.deinit(gpa); @@ -2827,7 +2839,6 @@ fn updateBuiltinModule(pt: Zcu.PerThread, opts: Builtin) Allocator.Error!struct .root_type = .none, }); - gop.value_ptr.* = mod; file_gop.key_ptr.* = file_index; zcu.module_roots.putAssumeCapacityNoClobber(mod, file_index.toOptional()); try opts.populateFile(gpa, file); @@ -2883,22 +2894,19 @@ pub fn embedFile( return @fromBackingInt(@intCast(gop.index)); } errdefer _ = zcu.embed_table.pop(); - gop.key_ptr.* = try pt.newEmbedFile(path); + try pt.initEmbedFile(gop.key_ptr, path); return @fromBackingInt(@intCast(gop.index)); } - const embed_file: *Zcu.EmbedFile, const embed_file_idx: Zcu.EmbedFile.Index = ef: { + const embed_file, const embed_file_idx: Zcu.EmbedFile.Index = ef: { const path = try cur_file.path.upJoin(gpa, zcu.comp.dirs, import_string); errdefer path.deinit(gpa); const gop = try zcu.embed_table.getOrPutAdapted(gpa, path, Zcu.EmbedTableAdapter{}); - if (gop.found_existing) { - path.deinit(gpa); // we're not using this key - break :ef .{ gop.key_ptr.*, @fromBackingInt(@intCast(gop.index)) }; - } else { + if (!gop.found_existing) { errdefer _ = zcu.embed_table.pop(); - gop.key_ptr.* = try pt.newEmbedFile(path); - break :ef .{ gop.key_ptr.*, @fromBackingInt(@intCast(gop.index)) }; - } + try pt.initEmbedFile(gop.key_ptr, path); + } else path.deinit(gpa); // we're not using this key + break :ef .{ gop.key_ptr, @fromBackingInt(@intCast(gop.index)) }; }; switch (embed_file.path.isNested(cur_file.mod.?.root)) { @@ -2913,7 +2921,7 @@ pub fn updateEmbedFile( pt: Zcu.PerThread, ef: *Zcu.EmbedFile, /// If not `null`, the interned file data is stored here, if it was loaded. - /// `newEmbedFile` uses this to add the file to the `whole` cache manifest. + /// `initEmbedFile` uses this to add the file to the `whole` cache manifest. ip_str_out: ?*?InternPool.String, ) Allocator.Error!void { pt.updateEmbedFileInner(ef, ip_str_out) catch |err| switch (err) { @@ -3000,17 +3008,13 @@ fn updateEmbedFileInner( } /// Assumes that `path` is allocated into `gpa`. Takes ownership of `path` on success. -fn newEmbedFile(pt: Zcu.PerThread, path: Compilation.Path) EmbedFileError!*Zcu.EmbedFile { +fn initEmbedFile(pt: Zcu.PerThread, ef: *Zcu.EmbedFile, path: Compilation.Path) EmbedFileError!void { const zcu = pt.zcu; const comp = zcu.comp; const io = comp.io; - const gpa = comp.gpa; const ip = &zcu.intern_pool; - const new_file = try gpa.create(Zcu.EmbedFile); - errdefer gpa.destroy(new_file); - - new_file.* = .{ + ef.* = .{ .path = path, .val = .none, .err = null, @@ -3018,7 +3022,7 @@ fn newEmbedFile(pt: Zcu.PerThread, path: Compilation.Path) EmbedFileError!*Zcu.E }; var opt_ip_str: ?InternPool.String = null; - try pt.updateEmbedFile(new_file, &opt_ip_str); + try pt.updateEmbedFile(ef, &opt_ip_str); try comp.appendFileSystemInput(path); @@ -3031,19 +3035,17 @@ fn newEmbedFile(pt: Zcu.PerThread, path: Compilation.Path) EmbedFileError!*Zcu.E const man = whole.cache_manifest orelse break :cache; const ip_str = opt_ip_str orelse break :cache; // this will be a compile error - const array_len = Value.fromInterned(new_file.val).typeOf(zcu).childType(zcu).arrayLen(zcu); + const array_len = Value.fromInterned(ef.val).typeOf(zcu).childType(zcu).arrayLen(zcu); const contents = ip_str.toSlice(array_len, ip); try whole.cache_manifest_mutex.lock(io); defer whole.cache_manifest_mutex.unlock(io); - path.addToCacheManifestAsDiscovered(man, &comp.dirs, contents, new_file.stat) catch |err| switch (err) { + path.addToCacheManifestAsDiscovered(man, &comp.dirs, contents, ef.stat) catch |err| switch (err) { error.FileSystemFailure => unreachable, // contents and stat are both provided else => |e| return e, }; } - - return new_file; } pub fn scanNamespace( @@ -3242,9 +3244,9 @@ const ScanDeclIter = struct { // Perhaps we should add all test indiscriminately and filter at the end of the update. if (!comp.config.is_test) break :a false; if (file.mod != zcu.main_mod) break :a false; - if (is_named and comp.test_filters.len > 0) { + if (is_named and zcu.test_filters.len > 0) { const fqn_slice = fqn.toSlice(ip); - for (comp.test_filters) |test_filter| { + for (zcu.test_filters) |test_filter| { if (std.mem.find(u8, fqn_slice, test_filter) != null) break; } else break :a false; } diff --git a/src/main.zig b/src/main.zig index 541504cdb218af539235a5e1f6abca83d30b7234..77a53032b45b49d1ee726244866ca2f2f117a27a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4413,13 +4413,12 @@ fn serve( switch (hdr.tag) { .exit => return cleanExit(io), .update => { - file_system_inputs.clearRetainingCapacity(); - if (arg_mode == .translate_c) { var arena_instance = std.heap.ArenaAllocator.init(gpa); defer arena_instance.deinit(); const arena = arena_instance.allocator(); var output: Compilation.TranslateCResult = undefined; + file_system_inputs.clearRetainingCapacity(); try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node, environ_map); defer output.deinit(gpa); @@ -4437,7 +4436,6 @@ fn serve( continue; } - try comp.makeBinFileWritable(); try comp.update(main_progress_node); try comp.makeBinFileExecutable(); @@ -4463,7 +4461,6 @@ fn serve( //); }, .hot_update => { - file_system_inputs.clearRetainingCapacity(); if (child_pid) |pid| { try comp.hotCodeSwap(main_progress_node, pid); try serveUpdateResults(&server, comp); diff --git a/test/src/Cases.zig b/test/src/Cases.zig index c6b3eb07d1ed6efdfd626952bac4ce484ca4efef..c6036823d92ffd09d752de600a25a354229a0de8 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -75,7 +75,6 @@ pub const Case = struct { emit_asm: bool = false, emit_bin: bool = true, - emit_h: bool = false, is_test: bool = false, expect_exact: bool = false, backend: Backend = .auto,