authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-02-12 23:33:04+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-10 10:26:12+00:00
log7ca061f3d68ff936dd77a58402c817b0aa1044e6
treebede78d3b45965979fc633233ae1ebe219367d37
parent986a4f1445e3ed1a6227a24d78b2154239ccdb31
signaturelock-open Commit is signed but in an unrecognized format.

compiler: rework and simplify main loop


4 files changed, 482 insertions(+), 613 deletions(-)

src/Compilation.zig+24-483
......@@ -21,7 +21,6 @@ const introspect = @import("introspect.zig");
2121const link = @import("link.zig");
2222const tracy = @import("tracy.zig");
2323const trace = tracy.trace;
24const traceNamed = tracy.traceNamed;
2524const build_options = @import("build_options");
2625const LibCInstallation = std.zig.LibCInstallation;
2726const glibc = @import("libs/glibc.zig");
......@@ -89,6 +88,9 @@ framework_dirs: []const []const u8,
8988/// These are only for DLLs dependencies fulfilled by the `.def` files shipped
9089/// with Zig. Static libraries are provided as `link.Input` values.
9190windows_libs: std.StringArrayHashMapUnmanaged(void),
91/// The number of items in `windows_libs` which we have already built. All items at or after this
92/// index will be built in `performAllTheWork`.
93windows_libs_num_done: u32,
9294version: ?std.SemanticVersion,
9395libc_installation: ?*const LibCInstallation,
9496skip_linker_dependencies: bool,
......@@ -126,8 +128,6 @@ oneshot_prelink_tasks: std.ArrayList(link.PrelinkTask),
126128/// work is queued or not.
127129queued_jobs: QueuedJobs,
128130
129work_queues: [2]std.Deque(Job),
130
131131/// These jobs are to invoke the Clang compiler to create an object file, which
132132/// gets linked with the Compilation.
133133c_object_work_queue: std.Deque(*CObject),
......@@ -954,51 +954,6 @@ pub const RcSourceFile = struct {
954954 extra_flags: []const []const u8 = &.{},
955955};
956956
957const Job = union(enum) {
958 /// Given the generated AIR for a function, put it onto the code generation queue.
959 /// MLUGG TODO: because type resolution is no longer necessary, we can remove this now
960 /// If the backend does not support `Zcu.Feature.separate_thread`, codegen and linking happen immediately.
961 /// Before queueing this `Job`, increase the estimated total item count for both
962 /// `comp.zcu.?.codegen_prog_node` and `comp.link_prog_node`.
963 codegen_func: struct {
964 func: InternPool.Index,
965 /// The AIR emitted from analyzing `func`; owned by this `Job` in `gpa`.
966 air: Air,
967 },
968 /// Queue a `link.ZcuTask` to emit this non-function `Nav` into the output binary.
969 /// MLUGG TODO: because type resolution is no longer necessary, we can remove this now
970 /// If the backend does not support `Zcu.Feature.separate_thread`, the task is run immediately.
971 /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`.
972 link_nav: InternPool.Nav.Index,
973 /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`.
974 update_line_number: InternPool.TrackedInst.Index,
975 /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed.
976 /// This may be its first time being analyzed, or it may be outdated.
977 /// If the unit is a function, a `codegen_func` job will be queued after analysis completes.
978 /// If the unit is a *test* function, an `analyze_func` job will also be queued.
979 analyze_unit: InternPool.AnalUnit,
980 /// The main source file for the module needs to be analyzed.
981 /// For every module which is an analysis root, analyze the main struct type of the module's
982 /// root source file. This is how semantic analysis begins.
983 analyze_roots,
984
985 /// The value is the index into `windows_libs`.
986 windows_import_lib: usize,
987
988 fn stage(job: *const Job) usize {
989 // Prioritize functions so that codegen can get to work on them on a
990 // separate thread, while Sema goes back to its own work.
991 return switch (job.*) {
992 .codegen_func => 0,
993 .analyze_unit => |unit| switch (unit.unwrap()) {
994 .func => 0,
995 else => 1,
996 },
997 else => 1,
998 };
999 }
1000};
1001
1002957pub const CObject = struct {
1003958 /// Relative to cwd. Owned by arena.
1004959 src: CSourceFile,
......@@ -2274,7 +2229,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
22742229 .root_mod = options.root_mod,
22752230 .config = options.config,
22762231 .dirs = options.dirs,
2277 .work_queues = @splat(.empty),
22782232 .c_object_work_queue = .empty,
22792233 .win32_resource_work_queue = .empty,
22802234 .c_source_files = options.c_source_files,
......@@ -2308,6 +2262,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
23082262 .root_name = root_name,
23092263 .sysroot = sysroot,
23102264 .windows_libs = .empty,
2265 .windows_libs_num_done = 0,
23112266 .version = options.version,
23122267 .libc_installation = libc_dirs.libc_installation,
23132268 .compiler_rt_strat = compiler_rt_strat,
......@@ -2670,16 +2625,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
26702625 }
26712626 }
26722627
2673 // Generate Windows import libs.
2674 if (target.os.tag == .windows) {
2675 const count = comp.windows_libs.count();
2676 for (0..count) |i| {
2677 try comp.queueJob(.{ .windows_import_lib = i });
2678 }
2679 // when integrating coff linker with prelink, the above `queueJob` will need to move
2680 // to something in `dispatchPrelinkWork`, which must queue all prelink link tasks
2681 // *before* we begin working on the main job queue.
2682 }
26832628 if (comp.wantBuildLibUnwindFromSource()) {
26842629 comp.queued_jobs.libunwind = true;
26852630 }
......@@ -2763,7 +2708,6 @@ pub fn destroy(comp: *Compilation) void {
27632708 if (comp.zcu) |zcu| zcu.deinit();
27642709 comp.cache_use.deinit(io);
27652710
2766 for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa);
27672711 comp.c_object_work_queue.deinit(gpa);
27682712 comp.win32_resource_work_queue.deinit(gpa);
27692713
......@@ -4563,13 +4507,7 @@ fn performAllTheWork(
45634507 comp: *Compilation,
45644508 main_progress_node: std.Progress.Node,
45654509 update_arena: Allocator,
4566) JobError!void {
4567 defer if (comp.zcu) |zcu| {
4568 zcu.codegen_task_pool.cancel(zcu);
4569 // Regardless of errors, `comp.zcu` needs to update its generation number.
4570 zcu.generation += 1;
4571 };
4572
4510) (Allocator.Error || Io.Cancelable)!void {
45734511 const io = comp.io;
45744512
45754513 // This is awkward: we don't want to start the timer until later, but we won't want to stop it
......@@ -4602,206 +4540,32 @@ fn performAllTheWork(
46024540 misc_group.async(io, workerDocsWasm, .{ comp, main_progress_node });
46034541 }
46044542
4605 if (comp.zcu) |zcu| {
4606 const tracy_trace = traceNamed(@src(), "astgen");
4607 defer tracy_trace.end();
4608
4609 const zir_prog_node = main_progress_node.start("AST Lowering", 0);
4610 defer zir_prog_node.end();
4611
4612 var timer = comp.startTimer();
4613 defer if (timer.finish(io)) |ns| {
4614 comp.mutex.lockUncancelable(io);
4615 defer comp.mutex.unlock(io);
4616 comp.time_report.?.stats.real_ns_files = ns;
4617 };
4618
4619 const gpa = comp.gpa;
4620
4621 var astgen_group: Io.Group = .init;
4622 defer astgen_group.cancel(io);
4623
4624 // We cannot reference `zcu.import_table` after we spawn any `workerUpdateFile` jobs,
4625 // because on single-threaded targets the worker will be run eagerly, meaning the
4626 // `import_table` could be mutated, and not even holding `comp.mutex` will save us. So,
4627 // build up a list of the files to update *before* we spawn any jobs.
4628 var astgen_work_items: std.MultiArrayList(struct {
4629 file_index: Zcu.File.Index,
4630 file: *Zcu.File,
4631 }) = .empty;
4632 defer astgen_work_items.deinit(gpa);
4633 // Not every item in `import_table` will need updating, because some are builtin.zig
4634 // files. However, most will, so let's just reserve sufficient capacity upfront.
4635 try astgen_work_items.ensureTotalCapacity(gpa, zcu.import_table.count());
4636 for (zcu.import_table.keys()) |file_index| {
4637 const file = zcu.fileByIndex(file_index);
4638 if (file.is_builtin) {
4639 // This is a `builtin.zig`, so updating is redundant. However, we want to make
4640 // sure the file contents are still correct on disk, since it can improve the
4641 // debugging experience better. That job only needs `file`, so we can kick it
4642 // off right now.
4643 astgen_group.async(io, workerUpdateBuiltinFile, .{ comp, file });
4644 continue;
4645 }
4646 astgen_work_items.appendAssumeCapacity(.{
4647 .file_index = file_index,
4648 .file = file,
4649 });
4650 }
4651
4652 // Now that we're not going to touch `zcu.import_table` again, we can spawn `workerUpdateFile` jobs.
4653 for (astgen_work_items.items(.file_index), astgen_work_items.items(.file)) |file_index, file| {
4654 astgen_group.async(io, workerUpdateFile, .{
4655 comp, file, file_index, zir_prog_node, &astgen_group,
4656 });
4657 }
4658
4659 // On the other hand, it's fine to directly iterate `zcu.embed_table.keys()` here
4660 // because `workerUpdateEmbedFile` can't invalidate it. The different here is that one
4661 // `@embedFile` can't trigger analysis of a new `@embedFile`!
4662 for (0.., zcu.embed_table.keys()) |ef_index_usize, ef| {
4663 const ef_index: Zcu.EmbedFile.Index = @enumFromInt(ef_index_usize);
4664 astgen_group.async(io, workerUpdateEmbedFile, .{
4665 comp, ef_index, ef,
4666 });
4667 }
4668
4669 try astgen_group.await(io);
4670 }
4671
4543 defer if (comp.zcu) |zcu| zcu.codegen_task_pool.cancel(zcu);
46724544 if (comp.zcu) |zcu| {
46734545 const pt: Zcu.PerThread = .activate(zcu, .main);
4674 defer pt.deactivate();
4675
4676 const gpa = zcu.gpa;
4677
4678 // On an incremental update, a source file might become "dead", in that all imports of
4679 // the file were removed. This could even change what module the file belongs to! As such,
4680 // we do a traversal over the files, to figure out which ones are alive and the modules
4681 // they belong to.
4682 const any_fatal_files = try pt.computeAliveFiles();
4683
4684 // If the cache mode is `whole`, add every alive source file to the manifest.
4685 switch (comp.cache_use) {
4686 .whole => |whole| if (whole.cache_manifest) |man| {
4687 for (zcu.alive_files.keys()) |file_index| {
4688 const file = zcu.fileByIndex(file_index);
4689
4690 switch (file.status) {
4691 .never_loaded => unreachable, // AstGen tried to load it
4692 .retryable_failure => continue, // the file cannot be read; this is a guaranteed error
4693 .astgen_failure, .success => {}, // the file was read successfully
4694 }
4695
4696 const path = try file.path.toAbsolute(comp.dirs, gpa);
4697 defer gpa.free(path);
4698
4699 const result = res: {
4700 try whole.cache_manifest_mutex.lock(io);
4701 defer whole.cache_manifest_mutex.unlock(io);
4702 if (file.source) |source| {
4703 break :res man.addFilePostContents(path, source, file.stat);
4704 } else {
4705 break :res man.addFilePost(path);
4706 }
4707 };
4708 result catch |err| switch (err) {
4709 error.OutOfMemory => |e| return e,
4710 else => {
4711 try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)});
4712 continue;
4713 },
4714 };
4715 }
4716 },
4717 .none, .incremental => {},
4718 }
4719
4720 if (any_fatal_files or
4721 zcu.multi_module_err != null or
4722 zcu.failed_imports.items.len > 0 or
4723 comp.alloc_failure_occurred)
4724 {
4725 // We give up right now! No updating of ZIR refs, no nothing. The idea is that this prevents
4726 // us from invalidating lots of incremental dependencies due to files with e.g. parse errors.
4727 // However, this means our analysis data is invalid, so we want to omit all analysis errors.
4728 zcu.skip_analysis_this_update = true;
4729 // Since we're skipping analysis, there are no ZCU link tasks.
4730 comp.link_queue.finishZcuQueue(comp);
4731 // Let other compilation work finish to collect as many errors as possible.
4732 try misc_group.await(io);
4733 comp.link_queue.wait(io);
4734 return;
4735 }
4736
4737 if (comp.time_report) |*tr| {
4738 tr.stats.n_reachable_files = @intCast(zcu.alive_files.count());
4739 }
4740
4741 if (comp.config.incremental) {
4742 const update_zir_refs_node = main_progress_node.start("Update ZIR References", 0);
4743 defer update_zir_refs_node.end();
4744 try pt.updateZirRefs();
4745 }
4746 try zcu.flushRetryableFailures();
4747
4748 // It's analysis time! Queue up our initial analysis.
4749 try comp.queueJob(.analyze_roots);
4750
4751 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
4752 if (comp.bin_file != null) {
4753 zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
4754 }
4755 // We increment `pending_codegen_jobs` so that it doesn't reach 0 until after analysis finishes.
4756 // That prevents the "Code Generation" node from constantly disappearing and reappearing when
4757 // we're probably going to analyze more functions at some point.
4758 assert(zcu.pending_codegen_jobs.swap(1, .monotonic) == 0); // don't let this become 0 until analysis finishes
4759 }
4760 // When analysis ends, delete the progress nodes for "Semantic Analysis" and possibly "Code Generation".
4761 defer if (comp.zcu) |zcu| {
4762 zcu.sema_prog_node.end();
4763 zcu.sema_prog_node = .none;
4764 if (zcu.pending_codegen_jobs.fetchSub(1, .monotonic) == 1) {
4765 // Decremented to 0, so all done.
4766 zcu.codegen_prog_node.end();
4767 zcu.codegen_prog_node = .none;
4768 }
4769 };
4770
4771 if (comp.zcu) |zcu| {
4772 if (!zcu.backendSupportsFeature(.separate_thread)) {
4773 // Close the ZCU task queue. Prelink may still be running, but the closed
4774 // queue will cause the linker task to exit once prelink finishes. The
4775 // closed queue also communicates to `enqueueZcu` that it should wait for
4776 // the linker task to finish and then run ZCU tasks serially.
4777 comp.link_queue.finishZcuQueue(comp);
4546 defer {
4547 pt.deactivate();
4548 // Regardless of errors, `comp.zcu` needs to update its generation number.
4549 zcu.generation += 1;
47784550 }
4551 try pt.update(main_progress_node, &decl_work_timer);
47794552 }
47804553
4781 if (comp.zcu != null) {
4782 // Start the timer for the "decls" part of the pipeline (Sema, CodeGen, link).
4783 decl_work_timer = comp.startTimer();
4784 }
4554 comp.link_queue.finishZcuQueue(comp);
47854555
4786 work: while (true) {
4787 for (&comp.work_queues) |*work_queue| if (work_queue.popFront()) |job| {
4788 try processOneJob(.main, comp, job);
4789 continue :work;
4556 // This has to happen after the main semantic analysis loop because it is possible for Sema to
4557 // call `addLinkLib` and hence add more items to `comp.windows_libs`.
4558 for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |link_lib| {
4559 mingw.buildImportLib(comp, link_lib) catch |err| {
4560 // TODO Surface more error details.
4561 comp.lockAndSetMiscFailure(
4562 .windows_import_lib,
4563 "unable to generate DLL import .lib file for {s}: {t}",
4564 .{ link_lib, err },
4565 );
47904566 };
4791 if (comp.zcu) |zcu| {
4792 // If there's no work queued, check if there's anything outdated
4793 // which we need to work on, and queue it if so.
4794 if (try zcu.findOutdatedToAnalyze()) |outdated| {
4795 try comp.queueJob(.{ .analyze_unit = outdated });
4796 continue;
4797 }
4798 zcu.sema_prog_node.end();
4799 zcu.sema_prog_node = .none;
4800 }
4801 break;
48024567 }
4803
4804 comp.link_queue.finishZcuQueue(comp);
4568 comp.windows_libs_num_done = @intCast(comp.windows_libs.count());
48054569
48064570 // Main thread work is all done, now just wait for all async work.
48074571 try misc_group.await(io);
......@@ -5032,121 +4796,6 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
50324796 };
50334797}
50344798
5035const JobError = Allocator.Error || Io.Cancelable;
5036
5037pub fn queueJob(comp: *Compilation, job: Job) !void {
5038 try comp.work_queues[job.stage()].pushBack(comp.gpa, job);
5039}
5040
5041pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void {
5042 for (jobs) |job| try comp.queueJob(job);
5043}
5044
5045fn processOneJob(tid: Zcu.PerThread.Id, comp: *Compilation, job: Job) JobError!void {
5046 switch (job) {
5047 .codegen_func => |func| {
5048 const zcu = comp.zcu.?;
5049 const gpa = zcu.gpa;
5050 var owned_air: ?Air = func.air;
5051 defer if (owned_air) |*air| air.deinit(gpa);
5052
5053 // Some linkers need to refer to the AIR. In that case, the linker is not running
5054 // concurrently, so we'll just keep ownership of the AIR for ourselves instead of
5055 // letting the codegen job destroy it.
5056 const disown_air = zcu.backendSupportsFeature(.separate_thread);
5057
5058 // Begin the codegen task. If the codegen/link queue is backed up, this might
5059 // block until the linker is able to process some tasks.
5060 const codegen_task = try zcu.codegen_task_pool.start(zcu, func.func, &owned_air.?, disown_air);
5061 if (disown_air) owned_air = null;
5062
5063 try comp.link_queue.enqueueZcu(comp, tid, .{ .link_func = codegen_task });
5064 },
5065 .link_nav => |nav_index| {
5066 const zcu = comp.zcu.?;
5067 const nav = zcu.intern_pool.getNav(nav_index);
5068 if (nav.analysis != null) {
5069 const unit: InternPool.AnalUnit = .wrap(.{ .nav_val = nav_index });
5070 if (zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit)) {
5071 comp.link_prog_node.completeOne();
5072 return;
5073 }
5074 }
5075 assert(nav.status == .fully_resolved);
5076 try comp.link_queue.enqueueZcu(comp, tid, .{ .link_nav = nav_index });
5077 },
5078 .update_line_number => |tracked_inst| {
5079 try comp.link_queue.enqueueZcu(comp, tid, .{ .debug_update_line_number = tracked_inst });
5080 },
5081 .analyze_unit => |unit| {
5082 const tracy_trace = traceNamed(@src(), "analyze_unit");
5083 defer tracy_trace.end();
5084
5085 const pt: Zcu.PerThread = .activate(comp.zcu.?, tid);
5086 defer pt.deactivate();
5087
5088 const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) {
5089 .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu),
5090 .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav, null),
5091 .nav_val => |nav| pt.ensureNavValUpToDate(nav, null),
5092 .type_layout => |ty| pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null),
5093 .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage, null),
5094 .func => |func| pt.ensureFuncBodyUpToDate(func, null),
5095 };
5096 maybe_err catch |err| switch (err) {
5097 error.OutOfMemory => |e| return e,
5098 error.Canceled => |e| return e,
5099 error.AnalysisFail => return,
5100 };
5101
5102 queue_test_analysis: {
5103 if (!comp.config.is_test) break :queue_test_analysis;
5104 const nav = switch (unit.unwrap()) {
5105 .nav_val => |nav| nav,
5106 else => break :queue_test_analysis,
5107 };
5108
5109 // Check if this is a test function.
5110 const ip = &pt.zcu.intern_pool;
5111 if (!pt.zcu.test_functions.contains(nav)) {
5112 break :queue_test_analysis;
5113 }
5114
5115 // Tests are always emitted in test binaries. The decl_refs are created by
5116 // Zcu.populateTestFunctions, but this will not queue body analysis, so do
5117 // that now.
5118 try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav).status.fully_resolved.val);
5119 }
5120 },
5121 .analyze_roots => {
5122 const tracy_trace = traceNamed(@src(), "analyze_roots");
5123 defer tracy_trace.end();
5124
5125 const zcu = comp.zcu.?;
5126 const pt: Zcu.PerThread = .activate(zcu, tid);
5127 defer pt.deactivate();
5128 for (zcu.analysisRoots()) |analysis_root_mod| {
5129 const analysis_root_file = zcu.module_roots.get(analysis_root_mod).?.unwrap().?;
5130 try pt.ensureFileAnalyzed(analysis_root_file);
5131 }
5132 },
5133 .windows_import_lib => |index| {
5134 const tracy_trace = traceNamed(@src(), "windows_import_lib");
5135 defer tracy_trace.end();
5136
5137 const link_lib = comp.windows_libs.keys()[index];
5138 mingw.buildImportLib(comp, link_lib) catch |err| {
5139 // TODO Surface more error details.
5140 comp.lockAndSetMiscFailure(
5141 .windows_import_lib,
5142 "unable to generate DLL import .lib file for {s}: {t}",
5143 .{ link_lib, err },
5144 );
5145 };
5146 },
5147 }
5148}
5149
51504799fn createDepFile(comp: *Compilation, dep_file: []const u8, bin_file: Cache.Path) anyerror!void {
51514800 const io = comp.io;
51524801
......@@ -5474,112 +5123,6 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
54745123 };
54755124}
54765125
5477fn workerUpdateFile(
5478 comp: *Compilation,
5479 file: *Zcu.File,
5480 file_index: Zcu.File.Index,
5481 prog_node: std.Progress.Node,
5482 group: *Io.Group,
5483) void {
5484 const io = comp.io;
5485 const tid: Zcu.PerThread.Id = .acquire(io);
5486 defer tid.release(io);
5487
5488 const child_prog_node = prog_node.start(fs.path.basename(file.path.sub_path), 0);
5489 defer child_prog_node.end();
5490
5491 const pt: Zcu.PerThread = .activate(comp.zcu.?, tid);
5492 defer pt.deactivate();
5493 pt.updateFile(file_index, file) catch |err| {
5494 pt.reportRetryableFileError(file_index, "unable to load '{s}': {s}", .{ fs.path.basename(file.path.sub_path), @errorName(err) }) catch |oom| switch (oom) {
5495 error.OutOfMemory => {
5496 comp.mutex.lockUncancelable(io);
5497 defer comp.mutex.unlock(io);
5498 comp.setAllocFailure();
5499 },
5500 };
5501 return;
5502 };
5503
5504 switch (file.getMode()) {
5505 .zig => {}, // continue to logic below
5506 .zon => return, // ZON can't import anything so we're done
5507 }
5508
5509 // Discover all imports in the file. Imports of modules we ignore for now since we don't
5510 // know which module we're in, but imports of file paths might need us to queue up other
5511 // AstGen jobs.
5512 const imports_index = file.zir.?.extra[@intFromEnum(Zir.ExtraIndex.imports)];
5513 if (imports_index != 0) {
5514 const extra = file.zir.?.extraData(Zir.Inst.Imports, imports_index);
5515 var import_i: u32 = 0;
5516 var extra_index = extra.end;
5517
5518 while (import_i < extra.data.imports_len) : (import_i += 1) {
5519 const item = file.zir.?.extraData(Zir.Inst.Imports.Item, extra_index);
5520 extra_index = item.end;
5521
5522 const import_path = file.zir.?.nullTerminatedString(item.data.name);
5523
5524 if (pt.discoverImport(file.path, import_path)) |res| switch (res) {
5525 .module, .existing_file => {},
5526 .new_file => |new| {
5527 group.async(io, workerUpdateFile, .{
5528 comp, new.file, new.index, prog_node, group,
5529 });
5530 },
5531 } else |err| switch (err) {
5532 error.OutOfMemory => {
5533 comp.mutex.lockUncancelable(io);
5534 defer comp.mutex.unlock(io);
5535 comp.setAllocFailure();
5536 },
5537 }
5538 }
5539 }
5540}
5541
5542fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void {
5543 Builtin.updateFileOnDisk(file, comp) catch |err| comp.lockAndSetMiscFailure(
5544 .write_builtin_zig,
5545 "unable to write '{f}': {s}",
5546 .{ file.path.fmt(comp), @errorName(err) },
5547 );
5548}
5549
5550fn workerUpdateEmbedFile(comp: *Compilation, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) void {
5551 const io = comp.io;
5552 const tid: Zcu.PerThread.Id = .acquire(io);
5553 defer tid.release(io);
5554 comp.detectEmbedFileUpdate(tid, ef_index, ef) catch |err| switch (err) {
5555 error.OutOfMemory => {
5556 comp.mutex.lockUncancelable(io);
5557 defer comp.mutex.unlock(io);
5558 comp.setAllocFailure();
5559 },
5560 };
5561}
5562
5563fn detectEmbedFileUpdate(comp: *Compilation, tid: Zcu.PerThread.Id, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) !void {
5564 const io = comp.io;
5565 const zcu = comp.zcu.?;
5566 const pt: Zcu.PerThread = .activate(zcu, tid);
5567 defer pt.deactivate();
5568
5569 const old_val = ef.val;
5570 const old_err = ef.err;
5571
5572 try pt.updateEmbedFile(ef, null);
5573
5574 if (ef.val != .none and ef.val == old_val) return; // success, value unchanged
5575 if (ef.val == .none and old_val == .none and ef.err == old_err) return; // failure, error unchanged
5576
5577 comp.mutex.lockUncancelable(io);
5578 defer comp.mutex.unlock(io);
5579
5580 try zcu.markDependeeOutdated(.not_marked_po, .{ .embed_file = ef_index });
5581}
5582
55835126pub fn obtainCObjectCacheManifest(
55845127 comp: *const Compilation,
55855128 owner_mod: *Package.Module,
......@@ -8208,12 +7751,10 @@ pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void {
82087751 // If we haven't seen this library yet and we're targeting Windows, we need
82097752 // to queue up a work item to produce the DLL import library for this.
82107753 const gop = try comp.windows_libs.getOrPut(comp.gpa, lib_name);
8211 if (gop.found_existing) return;
8212 {
7754 if (!gop.found_existing) {
82137755 errdefer _ = comp.windows_libs.pop();
82147756 gop.key_ptr.* = try comp.gpa.dupe(u8, lib_name);
82157757 }
8216 try comp.queueJob(.{ .windows_import_lib = gop.index });
82177758}
82187759
82197760/// This decides the optimization mode for all zig-provided libraries, including
src/Sema.zig+3-3
......@@ -5362,7 +5362,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
53625362 pt.updateFile(new_file_index, zcu.fileByIndex(new_file_index)) catch |err|
53635363 return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
53645364
5365 try pt.ensureFileAnalyzed(new_file_index);
5365 try pt.ensureFilePopulated(new_file_index);
53665366 const ty: Type = .fromInterned(zcu.fileRootType(new_file_index));
53675367 try sema.addTypeReferenceEntry(src, ty);
53685368 return .fromType(ty);
......@@ -13005,7 +13005,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
1300513005 const file = zcu.fileByIndex(file_index);
1300613006 switch (file.getMode()) {
1300713007 .zig => {
13008 try pt.ensureFileAnalyzed(file_index);
13008 try pt.ensureFilePopulated(file_index);
1300913009 const ty: Type = .fromInterned(zcu.fileRootType(file_index));
1301013010 try sema.addTypeReferenceEntry(operand_src, ty);
1301113011 // No need for `ensureNamespaceUpToDate`, because `Zcu.PerThread.updateFileNamespace`
......@@ -34002,7 +34002,7 @@ pub fn analyzeMemoizedState(sema: *Sema, stage: InternPool.MemoizedStateStage) C
3400234002 // Get the main struct type of the root source file of `std`. No need for a reference entry
3400334003 // because `std` is always an analysis root.
3400434004 const std_file_index = zcu.module_roots.get(zcu.std_mod).?.unwrap().?;
34005 try pt.ensureFileAnalyzed(std_file_index);
34005 try pt.ensureFilePopulated(std_file_index);
3400634006 const std_type: Type = .fromInterned(zcu.fileRootType(std_file_index));
3400734007 break :block .{
3400834008 .parent = null,
src/Zcu.zig+2
......@@ -3208,6 +3208,8 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
32083208/// recursive analysis (all of its previously-marked dependencies are already up-to-date), because
32093209/// recursive analysis can cause over-analysis on incremental updates.
32103210pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
3211 // MLUGG TODO: priorize `func` units, just like we used to do in the Compilation job queue.
3212
32113213 if (zcu.outdated_ready.count() > 0) {
32123214 const unit = zcu.outdated_ready.keys()[0];
32133215 log.debug("findOutdatedToAnalyze: {f}", .{zcu.fmtAnalUnit(unit)});
src/Zcu/PerThread.zig+453-127
......@@ -27,7 +27,9 @@ const introspect = @import("../introspect.zig");
2727const Module = @import("../Package.zig").Module;
2828const Sema = @import("../Sema.zig");
2929const target_util = @import("../target.zig");
30const trace = @import("../tracy.zig").trace;
30const tracy = @import("../tracy.zig");
31const trace = tracy.trace;
32const traceNamed = tracy.traceNamed;
3133const Type = @import("../Type.zig");
3234const Value = @import("../Value.zig");
3335const Zcu = @import("../Zcu.zig");
......@@ -125,6 +127,318 @@ pub fn deactivate(pt: Zcu.PerThread) void {
125127 pt.zcu.intern_pool.deactivate();
126128}
127129
130/// Called from `Compilation.performAllTheWork`. Performs one incremental update of the ZCU: detects
131/// changes to files, runs AstGen, and then enters the main semantic analysis loop, where we build
132/// up a graph of declarations, functions, etc, while also sending declarations and functions to
133/// codegen as they are analyzed.
134pub fn update(
135 pt: Zcu.PerThread,
136 main_progress_node: std.Progress.Node,
137 decl_work_timer: *?Compilation.Timer,
138) (Allocator.Error || Io.Cancelable)!void {
139 const zcu = pt.zcu;
140 const comp = zcu.comp;
141 const gpa = comp.gpa;
142 const io = comp.io;
143
144 {
145 const tracy_trace = traceNamed(@src(), "astgen");
146 defer tracy_trace.end();
147
148 const zir_prog_node = main_progress_node.start("AST Lowering", 0);
149 defer zir_prog_node.end();
150
151 var timer = comp.startTimer();
152 defer if (timer.finish(io)) |ns| {
153 comp.mutex.lockUncancelable(io);
154 defer comp.mutex.unlock(io);
155 comp.time_report.?.stats.real_ns_files = ns;
156 };
157
158 var astgen_group: Io.Group = .init;
159 defer astgen_group.cancel(io);
160
161 // We cannot reference `zcu.import_table` after we spawn any `workerUpdateFile` jobs,
162 // because on single-threaded targets the worker will be run eagerly, meaning the
163 // `import_table` could be mutated, and not even holding `comp.mutex` will save us. So,
164 // build up a list of the files to update *before* we spawn any jobs.
165 var astgen_work_items: std.MultiArrayList(struct {
166 file_index: Zcu.File.Index,
167 file: *Zcu.File,
168 }) = .empty;
169 defer astgen_work_items.deinit(gpa);
170 // Not every item in `import_table` will need updating, because some are builtin.zig
171 // files. However, most will, so let's just reserve sufficient capacity upfront.
172 try astgen_work_items.ensureTotalCapacity(gpa, zcu.import_table.count());
173 for (zcu.import_table.keys()) |file_index| {
174 const file = zcu.fileByIndex(file_index);
175 if (file.is_builtin) {
176 // This is a `builtin.zig`, so updating is redundant. However, we want to make
177 // sure the file contents are still correct on disk, since it can improve the
178 // debugging experience better. That job only needs `file`, so we can kick it
179 // off right now.
180 astgen_group.async(io, workerUpdateBuiltinFile, .{ comp, file });
181 continue;
182 }
183 astgen_work_items.appendAssumeCapacity(.{
184 .file_index = file_index,
185 .file = file,
186 });
187 }
188
189 // Now that we're not going to touch `zcu.import_table` again, we can spawn `workerUpdateFile` jobs.
190 for (astgen_work_items.items(.file_index), astgen_work_items.items(.file)) |file_index, file| {
191 astgen_group.async(io, workerUpdateFile, .{
192 comp, file, file_index, zir_prog_node, &astgen_group,
193 });
194 }
195
196 // On the other hand, it's fine to directly iterate `zcu.embed_table.keys()` here
197 // because `workerUpdateEmbedFile` can't invalidate it. The different here is that one
198 // `@embedFile` can't trigger analysis of a new `@embedFile`!
199 for (0.., zcu.embed_table.keys()) |ef_index_usize, ef| {
200 const ef_index: Zcu.EmbedFile.Index = @enumFromInt(ef_index_usize);
201 astgen_group.async(io, workerUpdateEmbedFile, .{
202 comp, ef_index, ef,
203 });
204 }
205
206 try astgen_group.await(io);
207 }
208
209 // On an incremental update, a source file might become "dead", in that all imports of
210 // the file were removed. This could even change what module the file belongs to! As such,
211 // we do a traversal over the files, to figure out which ones are alive and the modules
212 // they belong to.
213 const any_fatal_files = try pt.computeAliveFiles();
214
215 // If the cache mode is `whole`, add every alive source file to the manifest.
216 switch (comp.cache_use) {
217 .whole => |whole| if (whole.cache_manifest) |man| {
218 for (zcu.alive_files.keys()) |file_index| {
219 const file = zcu.fileByIndex(file_index);
220
221 switch (file.status) {
222 .never_loaded => unreachable, // AstGen tried to load it
223 .retryable_failure => continue, // the file cannot be read; this is a guaranteed error
224 .astgen_failure, .success => {}, // the file was read successfully
225 }
226
227 const path = try file.path.toAbsolute(comp.dirs, gpa);
228 defer gpa.free(path);
229
230 const result = res: {
231 try whole.cache_manifest_mutex.lock(io);
232 defer whole.cache_manifest_mutex.unlock(io);
233 if (file.source) |source| {
234 break :res man.addFilePostContents(path, source, file.stat);
235 } else {
236 break :res man.addFilePost(path);
237 }
238 };
239 result catch |err| switch (err) {
240 error.OutOfMemory => |e| return e,
241 else => {
242 try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)});
243 continue;
244 },
245 };
246 }
247 },
248 .none, .incremental => {},
249 }
250
251 if (comp.time_report) |*tr| {
252 tr.stats.n_reachable_files = @intCast(zcu.alive_files.count());
253 }
254
255 if (any_fatal_files or
256 zcu.multi_module_err != null or
257 zcu.failed_imports.items.len > 0 or
258 comp.alloc_failure_occurred)
259 {
260 // We give up right now! No updating of ZIR refs, no nothing. The idea is that this prevents
261 // us from invalidating lots of incremental dependencies due to files with e.g. parse errors.
262 // However, this means our analysis data is invalid, so we want to omit all analysis errors.
263 zcu.skip_analysis_this_update = true;
264 return;
265 }
266
267 if (comp.config.incremental) {
268 const update_zir_refs_node = main_progress_node.start("Update ZIR References", 0);
269 defer update_zir_refs_node.end();
270 try pt.updateZirRefs();
271 }
272
273 try zcu.flushRetryableFailures();
274
275 if (!zcu.backendSupportsFeature(.separate_thread)) {
276 // Close the ZCU task queue. Prelink may still be running, but the closed
277 // queue will cause the linker task to exit once prelink finishes. The
278 // closed queue also communicates to `enqueueZcu` that it should wait for
279 // the linker task to finish and then run ZCU tasks serially.
280 comp.link_queue.finishZcuQueue(comp);
281 }
282
283 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
284 if (comp.bin_file != null) {
285 zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
286 }
287 // We increment `pending_codegen_jobs` so that it doesn't reach 0 until after analysis finishes.
288 // That prevents the "Code Generation" node from constantly disappearing and reappearing when
289 // we're probably going to analyze more functions at some point.
290 assert(zcu.pending_codegen_jobs.swap(1, .monotonic) == 0); // don't let this become 0 until analysis finishes
291
292 defer {
293 zcu.sema_prog_node.end();
294 zcu.sema_prog_node = .none;
295 if (zcu.pending_codegen_jobs.fetchSub(1, .monotonic) == 1) {
296 // Decremented to 0, so all done.
297 zcu.codegen_prog_node.end();
298 zcu.codegen_prog_node = .none;
299 }
300 }
301
302 // Start the timer for the "decls" part of the pipeline (Sema, CodeGen, link).
303 decl_work_timer.* = comp.startTimer();
304
305 // To kick off semantic analysis, populate the root source file of any module we have marked
306 // as an analysis root. Declarations in these files which want eager analysis---those being
307 // `comptime` declarations, any declarations marked `export`, and `test` declarations in the
308 // main module if this is a test compilation---become referenced, and so will be picked up
309 // up by the main semantic analysis loop below.
310 for (zcu.analysisRoots()) |analysis_root_mod| {
311 const analysis_root_file = zcu.module_roots.get(analysis_root_mod).?.unwrap().?;
312 try pt.ensureFilePopulated(analysis_root_file);
313 }
314
315 // This is the main semantic analysis loop, which is essentially the main loop of the whole
316 // Zig compilation pipeline. It selects some `AnalUnit` which we know needs to be analyzed,
317 // and analyzes it, which may in turn discover more `AnalUnit`s which we need to analyze.
318 while (try zcu.findOutdatedToAnalyze()) |unit| {
319 const tracy_trace = traceNamed(@src(), "analyze_outdated");
320 defer tracy_trace.end();
321
322 const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) {
323 .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu),
324 .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav, null),
325 .nav_val => |nav| pt.ensureNavValUpToDate(nav, null),
326 .type_layout => |ty| pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null),
327 .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage, null),
328 .func => |func| pt.ensureFuncBodyUpToDate(func, null),
329 };
330 maybe_err catch |err| switch (err) {
331 error.OutOfMemory,
332 error.Canceled,
333 => |e| return e,
334
335 error.AnalysisFail => {}, // already reported
336 };
337 }
338}
339fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void {
340 Builtin.updateFileOnDisk(file, comp) catch |err| comp.lockAndSetMiscFailure(
341 .write_builtin_zig,
342 "unable to write '{f}': {s}",
343 .{ file.path.fmt(comp), @errorName(err) },
344 );
345}
346fn workerUpdateFile(
347 comp: *Compilation,
348 file: *Zcu.File,
349 file_index: Zcu.File.Index,
350 prog_node: std.Progress.Node,
351 group: *Io.Group,
352) void {
353 const io = comp.io;
354 const tid: Zcu.PerThread.Id = .acquire(io);
355 defer tid.release(io);
356
357 const child_prog_node = prog_node.start(std.fs.path.basename(file.path.sub_path), 0);
358 defer child_prog_node.end();
359
360 const pt: Zcu.PerThread = .activate(comp.zcu.?, tid);
361 defer pt.deactivate();
362 pt.updateFile(file_index, file) catch |err| {
363 pt.reportRetryableFileError(file_index, "unable to load '{s}': {s}", .{ std.fs.path.basename(file.path.sub_path), @errorName(err) }) catch |oom| switch (oom) {
364 error.OutOfMemory => {
365 comp.mutex.lockUncancelable(io);
366 defer comp.mutex.unlock(io);
367 comp.setAllocFailure();
368 },
369 };
370 return;
371 };
372
373 switch (file.getMode()) {
374 .zig => {}, // continue to logic below
375 .zon => return, // ZON can't import anything so we're done
376 }
377
378 // Discover all imports in the file. Imports of modules we ignore for now since we don't
379 // know which module we're in, but imports of file paths might need us to queue up other
380 // AstGen jobs.
381 const imports_index = file.zir.?.extra[@intFromEnum(Zir.ExtraIndex.imports)];
382 if (imports_index != 0) {
383 const extra = file.zir.?.extraData(Zir.Inst.Imports, imports_index);
384 var import_i: u32 = 0;
385 var extra_index = extra.end;
386
387 while (import_i < extra.data.imports_len) : (import_i += 1) {
388 const item = file.zir.?.extraData(Zir.Inst.Imports.Item, extra_index);
389 extra_index = item.end;
390
391 const import_path = file.zir.?.nullTerminatedString(item.data.name);
392
393 if (pt.discoverImport(file.path, import_path)) |res| switch (res) {
394 .module, .existing_file => {},
395 .new_file => |new| {
396 group.async(io, workerUpdateFile, .{
397 comp, new.file, new.index, prog_node, group,
398 });
399 },
400 } else |err| switch (err) {
401 error.OutOfMemory => {
402 comp.mutex.lockUncancelable(io);
403 defer comp.mutex.unlock(io);
404 comp.setAllocFailure();
405 },
406 }
407 }
408 }
409}
410fn workerUpdateEmbedFile(comp: *Compilation, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) void {
411 const io = comp.io;
412 const tid: Zcu.PerThread.Id = .acquire(io);
413 defer tid.release(io);
414 detectEmbedFileUpdate(comp, tid, ef_index, ef) catch |err| switch (err) {
415 error.OutOfMemory => {
416 comp.mutex.lockUncancelable(io);
417 defer comp.mutex.unlock(io);
418 comp.setAllocFailure();
419 },
420 };
421}
422fn detectEmbedFileUpdate(comp: *Compilation, tid: Zcu.PerThread.Id, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) !void {
423 const io = comp.io;
424 const zcu = comp.zcu.?;
425 const pt: Zcu.PerThread = .activate(zcu, tid);
426 defer pt.deactivate();
427
428 const old_val = ef.val;
429 const old_err = ef.err;
430
431 try pt.updateEmbedFile(ef, null);
432
433 if (ef.val != .none and ef.val == old_val) return; // success, value unchanged
434 if (ef.val == .none and old_val == .none and ef.err == old_err) return; // failure, error unchanged
435
436 comp.mutex.lockUncancelable(io);
437 defer comp.mutex.unlock(io);
438
439 try zcu.markDependeeOutdated(.not_marked_po, .{ .embed_file = ef_index });
440}
441
128442fn deinitFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void {
129443 const zcu = pt.zcu;
130444 const gpa = zcu.gpa;
......@@ -156,8 +470,8 @@ pub fn updateFile(
156470) !void {
157471 dev.check(.ast_gen);
158472
159 const tracy = trace(@src());
160 defer tracy.end();
473 const tracy_trace = trace(@src());
474 defer tracy_trace.end();
161475
162476 const zcu = pt.zcu;
163477 const comp = zcu.comp;
......@@ -484,7 +798,7 @@ fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.AutoArrayHashMapUnman
484798 updated_files.deinit(gpa);
485799}
486800
487pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
801fn updateZirRefs(pt: Zcu.PerThread) (Io.Cancelable || Allocator.Error)!void {
488802 assert(pt.tid == .main);
489803 const zcu = pt.zcu;
490804 const comp = zcu.comp;
......@@ -566,7 +880,7 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
566880 const old_line = old_zir.getDeclaration(old_inst).src_line;
567881 const new_line = new_zir.getDeclaration(new_inst).src_line;
568882 if (old_line != new_line) {
569 try comp.queueJob(.{ .update_line_number = tracked_inst_index });
883 try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .debug_update_line_number = tracked_inst_index });
570884 }
571885 },
572886 else => {},
......@@ -674,11 +988,11 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
674988/// Typical Zig compilations begin by claling this function on the root source file of the standard
675989/// library, `lib/std/std.zig`. The resulting namespace scan discovers a `comptime` declaration in
676990/// that file, which is queued for analysis, and everything goes from there.
677pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) (Allocator.Error || Io.Cancelable)!void {
991pub fn ensureFilePopulated(pt: Zcu.PerThread, file_index: Zcu.File.Index) (Allocator.Error || Io.Cancelable)!void {
678992 dev.check(.sema);
679993
680 const tracy = trace(@src());
681 defer tracy.end();
994 const tracy_trace = trace(@src());
995 defer tracy_trace.end();
682996
683997 const zcu = pt.zcu;
684998 const comp = zcu.comp;
......@@ -734,8 +1048,8 @@ pub fn ensureMemoizedStateUpToDate(
7341048 /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`.
7351049 reason: ?*const Zcu.DependencyReason,
7361050) Zcu.SemaError!void {
737 const tracy = trace(@src());
738 defer tracy.end();
1051 const tracy_trace = trace(@src());
1052 defer tracy_trace.end();
7391053
7401054 const zcu = pt.zcu;
7411055 const gpa = zcu.gpa;
......@@ -844,8 +1158,8 @@ fn analyzeMemoizedState(
8441158/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is
8451159/// free to ignore this, since the error is already registered.
8461160pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.SemaError!void {
847 const tracy = trace(@src());
848 defer tracy.end();
1161 const tracy_trace = trace(@src());
1162 defer tracy_trace.end();
8491163
8501164 const zcu = pt.zcu;
8511165 const gpa = zcu.gpa;
......@@ -1008,8 +1322,8 @@ pub fn ensureTypeLayoutUpToDate(
10081322 /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`.
10091323 reason: ?*const Zcu.DependencyReason,
10101324) Zcu.SemaError!void {
1011 const tracy = trace(@src());
1012 defer tracy.end();
1325 const tracy_trace = trace(@src());
1326 defer tracy_trace.end();
10131327
10141328 const zcu = pt.zcu;
10151329 const comp = zcu.comp;
......@@ -1121,8 +1435,8 @@ pub fn ensureNavValUpToDate(
11211435 /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`.
11221436 reason: ?*const Zcu.DependencyReason,
11231437) Zcu.SemaError!void {
1124 const tracy = trace(@src());
1125 defer tracy.end();
1438 const tracy_trace = trace(@src());
1439 defer tracy_trace.end();
11261440
11271441 const zcu = pt.zcu;
11281442 const gpa = zcu.gpa;
......@@ -1457,12 +1771,20 @@ fn analyzeNavVal(
14571771 if (!queue_linker_work) break :queue_codegen;
14581772
14591773 if (!nav_ty.hasRuntimeBits(zcu)) {
1460 if (zcu.comp.config.use_llvm) break :queue_codegen;
1774 if (comp.config.use_llvm) break :queue_codegen;
14611775 if (file.mod.?.strip) break :queue_codegen;
14621776 }
14631777
1464 zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
1465 try zcu.comp.queueJob(.{ .link_nav = nav_id });
1778 comp.link_prog_node.increaseEstimatedTotalItems(1);
1779 try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .link_nav = nav_id });
1780 }
1781
1782 if (comp.config.is_test and zcu.test_functions.contains(nav_id)) {
1783 // We just analyzed a test function's "value" (essentially its signature); now we need to
1784 // implicitly reference the function *body*. `Zcu.resolveReferences` knows about this rule,
1785 // so we don't need to mark an explicit reference, but we do need to make sure that the test
1786 // body will actually get analyzed!
1787 try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern());
14661788 }
14671789
14681790 switch (old_nav.status) {
......@@ -1477,8 +1799,8 @@ pub fn ensureNavTypeUpToDate(
14771799 /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`.
14781800 reason: ?*const Zcu.DependencyReason,
14791801) Zcu.SemaError!void {
1480 const tracy = trace(@src());
1481 defer tracy.end();
1802 const tracy_trace = trace(@src());
1803 defer tracy_trace.end();
14821804
14831805 const zcu = pt.zcu;
14841806 const gpa = zcu.gpa;
......@@ -1719,8 +2041,8 @@ pub fn ensureFuncBodyUpToDate(
17192041) Zcu.SemaError!void {
17202042 dev.check(.sema);
17212043
1722 const tracy = trace(@src());
1723 defer tracy.end();
2044 const tracy_trace = trace(@src());
2045 defer tracy_trace.end();
17242046
17252047 const zcu = pt.zcu;
17262048 const gpa = zcu.gpa;
......@@ -1846,7 +2168,8 @@ fn analyzeFuncBody(
18462168 log.debug("analyze and generate fn body {f}", .{zcu.fmtAnalUnit(anal_unit)});
18472169
18482170 var air = try pt.analyzeFuncBodyInner(func_index, reason);
1849 errdefer air.deinit(gpa);
2171 var air_owned = true;
2172 errdefer if (air_owned) air.deinit(gpa);
18502173
18512174 const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or
18522175 func.resolvedErrorSetUnordered(ip) != old_resolved_ies;
......@@ -1856,17 +2179,22 @@ fn analyzeFuncBody(
18562179 const dump_air = build_options.enable_debug_extensions and comp.verbose_air;
18572180 const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null);
18582181
1859 if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) {
1860 air.deinit(gpa);
1861 return .{ .ies_outdated = ies_outdated };
1862 }
2182 if (comp.bin_file != null or zcu.llvm_object != null or dump_air or dump_llvm_ir) {
2183 zcu.codegen_prog_node.increaseEstimatedTotalItems(1);
2184 comp.link_prog_node.increaseEstimatedTotalItems(1);
18632185
1864 zcu.codegen_prog_node.increaseEstimatedTotalItems(1);
1865 comp.link_prog_node.increaseEstimatedTotalItems(1);
1866 try comp.queueJob(.{ .codegen_func = .{
1867 .func = func_index,
1868 .air = air,
1869 } });
2186 // Some linkers need to refer to the AIR. In that case, the linker is not running
2187 // concurrently, so we'll just keep ownership of the AIR for ourselves instead of
2188 // letting the codegen job destroy it.
2189 const disown_air = zcu.backendSupportsFeature(.separate_thread);
2190
2191 // Begin the codegen task. If the codegen/link queue is backed up, this might
2192 // block until the linker is able to process some tasks.
2193 const codegen_task = try zcu.codegen_task_pool.start(zcu, func_index, &air, disown_air);
2194 if (disown_air) air_owned = false;
2195
2196 try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .link_func = codegen_task });
2197 }
18702198
18712199 return .{ .ies_outdated = ies_outdated };
18722200}
......@@ -2121,7 +2449,7 @@ pub fn populateModuleRootTable(pt: Zcu.PerThread) error{
21212449/// modify `pt.zcu.skip_analysis_this_update`.
21222450///
21232451/// If an error is returned, `pt.zcu.alive_files` might contain undefined values.
2124pub fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool {
2452fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool {
21252453 const zcu = pt.zcu;
21262454 const comp = zcu.comp;
21272455 const gpa = zcu.gpa;
......@@ -2562,8 +2890,8 @@ pub fn scanNamespace(
25622890 namespace_index: Zcu.Namespace.Index,
25632891 decls: []const Zir.Inst.Index,
25642892) Allocator.Error!void {
2565 const tracy = trace(@src());
2566 defer tracy.end();
2893 const tracy_trace = trace(@src());
2894 defer tracy_trace.end();
25672895
25682896 const zcu = pt.zcu;
25692897 const ip = &zcu.intern_pool;
......@@ -2659,8 +2987,8 @@ const ScanDeclIter = struct {
26592987 }
26602988
26612989 fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void {
2662 const tracy = trace(@src());
2663 defer tracy.end();
2990 const tracy_trace = trace(@src());
2991 defer tracy_trace.end();
26642992
26652993 const pt = iter.pt;
26662994 const zcu = pt.zcu;
......@@ -2713,77 +3041,65 @@ const ScanDeclIter = struct {
27133041
27143042 const existing_unit = iter.existing_by_inst.get(tracked_inst);
27153043
2716 const unit: AnalUnit, const want_analysis = switch (decl.kind) {
2717 .@"comptime" => unit: {
2718 const cu = if (existing_unit) |eu|
2719 eu.unwrap().@"comptime"
2720 else
2721 try ip.createComptimeUnit(gpa, io, pt.tid, tracked_inst, namespace_index);
2722
3044 const name = maybe_name.unwrap() orelse {
3045 // Only `comptime` declarations are unnamed.
3046 assert(decl.kind == .@"comptime");
3047 if (existing_unit) |unit| {
3048 try namespace.comptime_decls.append(gpa, unit.unwrap().@"comptime");
3049 } else {
3050 const cu = try ip.createComptimeUnit(gpa, io, pt.tid, tracked_inst, namespace_index);
3051 try zcu.queueComptimeUnitAnalysis(cu);
27233052 try namespace.comptime_decls.append(gpa, cu);
3053 }
3054 return;
3055 };
27243056
2725 if (existing_unit == null) {
2726 // For a `comptime` declaration, whether to analyze is based solely on whether the unit
2727 // is outdated. So, add this fresh one to `outdated` and `outdated_ready`.
2728 try zcu.queueComptimeUnitAnalysis(cu);
2729 }
3057 const fqn = try namespace.internFullyQualifiedName(ip, gpa, io, pt.tid, name);
3058
3059 const nav = if (existing_unit) |unit| nav: {
3060 const nav = unit.unwrap().nav_val;
3061 assert(ip.getNav(nav).name == name);
3062 assert(ip.getNav(nav).fqn == fqn);
3063 break :nav nav;
3064 } else nav: {
3065 const nav = try ip.createDeclNav(gpa, io, pt.tid, name, fqn, tracked_inst, namespace_index);
3066 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav);
3067 break :nav nav;
3068 };
27303069
2731 break :unit .{ .wrap(.{ .@"comptime" = cu }), true };
3070 const want_analysis: bool = switch (decl.kind) {
3071 .@"comptime" => unreachable,
3072 .unnamed_test, .@"test", .decltest => a: {
3073 const is_named = decl.kind != .unnamed_test;
3074 try namespace.test_decls.append(gpa, nav);
3075 // TODO: incremental compilation!
3076 // * remove from `test_functions` if no longer matching filter
3077 // * add to `test_functions` if newly passing filter
3078 // This logic is unaware of incremental: we'll end up with duplicates.
3079 // Perhaps we should add all test indiscriminately and filter at the end of the update.
3080 if (!comp.config.is_test) break :a false;
3081 if (file.mod != zcu.main_mod) break :a false;
3082 if (is_named and comp.test_filters.len > 0) {
3083 const fqn_slice = fqn.toSlice(ip);
3084 for (comp.test_filters) |test_filter| {
3085 if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break;
3086 } else break :a false;
3087 }
3088 try zcu.test_functions.put(gpa, nav, {});
3089 break :a true;
27323090 },
2733 else => unit: {
2734 const name = maybe_name.unwrap().?;
2735 const fqn = try namespace.internFullyQualifiedName(ip, gpa, io, pt.tid, name);
2736 const nav = if (existing_unit) |eu| eu.unwrap().nav_val else nav: {
2737 const nav = try ip.createDeclNav(gpa, io, pt.tid, name, fqn, tracked_inst, namespace_index);
2738 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav);
2739 break :nav nav;
2740 };
2741
2742 const unit: AnalUnit = .wrap(.{ .nav_val = nav });
2743
2744 assert(ip.getNav(nav).name == name);
2745 assert(ip.getNav(nav).fqn == fqn);
2746
2747 const want_analysis = switch (decl.kind) {
2748 .@"comptime" => unreachable,
2749 .unnamed_test, .@"test", .decltest => a: {
2750 const is_named = decl.kind != .unnamed_test;
2751 try namespace.test_decls.append(gpa, nav);
2752 // TODO: incremental compilation!
2753 // * remove from `test_functions` if no longer matching filter
2754 // * add to `test_functions` if newly passing filter
2755 // This logic is unaware of incremental: we'll end up with duplicates.
2756 // Perhaps we should add all test indiscriminately and filter at the end of the update.
2757 if (!comp.config.is_test) break :a false;
2758 if (file.mod != zcu.main_mod) break :a false;
2759 if (is_named and comp.test_filters.len > 0) {
2760 const fqn_slice = fqn.toSlice(ip);
2761 for (comp.test_filters) |test_filter| {
2762 if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break;
2763 } else break :a false;
2764 }
2765 try zcu.test_functions.put(gpa, nav, {});
2766 break :a true;
2767 },
2768 .@"const", .@"var" => a: {
2769 if (decl.is_pub) {
2770 try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
2771 } else {
2772 try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
2773 }
2774 break :a false;
2775 },
2776 };
2777 break :unit .{ unit, want_analysis };
3091 .@"const", .@"var" => a: {
3092 if (decl.is_pub) {
3093 try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
3094 } else {
3095 try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
3096 }
3097 break :a false;
27783098 },
27793099 };
27803100
2781 if (existing_unit == null and (want_analysis or decl.linkage == .@"export")) {
2782 log.debug(
2783 "scanDecl queue analyze_unit file='{s}' unit={f}",
2784 .{ namespace.fileScope(zcu).sub_file_path, zcu.fmtAnalUnit(unit) },
2785 );
2786 try comp.queueJob(.{ .analyze_unit = unit });
3101 if (want_analysis or decl.linkage == .@"export") {
3102 try zcu.ensureNavValAnalysisQueued(nav);
27873103 }
27883104 }
27893105};
......@@ -2793,8 +3109,8 @@ fn analyzeFuncBodyInner(
27933109 func_index: InternPool.Index,
27943110 reason: ?*const Zcu.DependencyReason,
27953111) Zcu.SemaError!Air {
2796 const tracy = trace(@src());
2797 defer tracy.end();
3112 const tracy_trace = trace(@src());
3113 defer tracy_trace.end();
27983114
27993115 const zcu = pt.zcu;
28003116 const comp = zcu.comp;
......@@ -3437,36 +3753,45 @@ pub fn intern(pt: Zcu.PerThread, key: InternPool.Key) Allocator.Error!InternPool
34373753
34383754/// Essentially a shortcut for calling `intern_pool.getCoerced`.
34393755/// However, this function also allows coercing `extern`s. The `InternPool` function can't do
3440/// this because it requires potentially pushing to the job queue.
3756/// this because it requires potentially queueing a link task.
34413757pub fn getCoerced(pt: Zcu.PerThread, val: Value, new_ty: Type) Allocator.Error!Value {
34423758 const ip = &pt.zcu.intern_pool;
34433759 const comp = pt.zcu.comp;
34443760 const gpa = comp.gpa;
34453761 const io = comp.io;
34463762 switch (ip.indexToKey(val.toIntern())) {
3447 .@"extern" => |e| {
3448 const coerced = try pt.getExtern(.{
3449 .name = e.name,
3763 .@"extern" => |@"extern"| {
3764 // TODO: it's awkward to make this function cancelable. The problem is really that
3765 // `getCoerced` is a bad API: it should be replaced with smaller, more specialized
3766 // functions, so that this cancel point is only possible in the rare case that you
3767 // may actually need to coerce an extern!
3768 const old_prot = io.swapCancelProtection(.blocked);
3769 defer _ = io.swapCancelProtection(old_prot);
3770 const coerced = pt.getExtern(.{
3771 .name = @"extern".name,
34503772 .ty = new_ty.toIntern(),
3451 .lib_name = e.lib_name,
3452 .is_const = e.is_const,
3453 .is_threadlocal = e.is_threadlocal,
3454 .linkage = e.linkage,
3455 .visibility = e.visibility,
3456 .is_dll_import = e.is_dll_import,
3457 .relocation = e.relocation,
3458 .decoration = e.decoration,
3459 .alignment = e.alignment,
3460 .@"addrspace" = e.@"addrspace",
3461 .zir_index = e.zir_index,
3773 .lib_name = @"extern".lib_name,
3774 .is_const = @"extern".is_const,
3775 .is_threadlocal = @"extern".is_threadlocal,
3776 .linkage = @"extern".linkage,
3777 .visibility = @"extern".visibility,
3778 .is_dll_import = @"extern".is_dll_import,
3779 .relocation = @"extern".relocation,
3780 .decoration = @"extern".decoration,
3781 .alignment = @"extern".alignment,
3782 .@"addrspace" = @"extern".@"addrspace",
3783 .zir_index = @"extern".zir_index,
34623784 .owner_nav = undefined, // ignored by `getExtern`.
3463 .source = e.source,
3464 });
3465 return Value.fromInterned(coerced);
3785 .source = @"extern".source,
3786 }) catch |err| switch (err) {
3787 error.Canceled => unreachable, // blocked above
3788 error.OutOfMemory => |e| return e,
3789 };
3790 return .fromInterned(coerced);
34663791 },
34673792 else => {},
34683793 }
3469 return Value.fromInterned(try ip.getCoerced(gpa, io, pt.tid, val.toIntern(), new_ty.toIntern()));
3794 return .fromInterned(try ip.getCoerced(gpa, io, pt.tid, val.toIntern(), new_ty.toIntern()));
34703795}
34713796
34723797pub fn intType(pt: Zcu.PerThread, signedness: std.builtin.Signedness, bits: u16) Allocator.Error!Type {
......@@ -3865,14 +4190,15 @@ pub fn navPtrType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Allocator.Err
38654190/// Intern an `.@"extern"`, creating a corresponding owner `Nav` if necessary.
38664191/// If necessary, the new `Nav` is queued for codegen.
38674192/// `key.owner_nav` is ignored and may be `undefined`.
3868pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!InternPool.Index {
4193pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) (Io.Cancelable || Allocator.Error)!InternPool.Index {
38694194 const zcu = pt.zcu;
38704195 const comp = zcu.comp;
4196 Type.fromInterned(key.ty).assertHasLayout(zcu);
38714197 const result = try zcu.intern_pool.getExtern(comp.gpa, comp.io, pt.tid, key);
38724198 if (result.new_nav.unwrap()) |nav| {
3873 comp.link_prog_node.increaseEstimatedTotalItems(1);
3874 try comp.queueJob(.{ .link_nav = nav });
38754199 if (comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav);
4200 comp.link_prog_node.increaseEstimatedTotalItems(1);
4201 try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .link_nav = nav });
38764202 }
38774203 return result.index;
38784204}