authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-02-13 09:31:36+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-10 10:26:12+00:00
log6402e119e8d21403cbafc4a6c8ca353b894592ba
tree95ae84233c2701f8b5a7f4579e8ddd8d6bc7a565
parent7ca061f3d68ff936dd77a58402c817b0aa1044e6
signaturelock-open Commit is signed but in an unrecognized format.

Zcu: prioritize analyzing function bodies

...so that they can be sent to the codegen backend and linker ASAP.

1 files changed, 63 insertions(+), 25 deletions(-)

src/Zcu.zig+63-25
......@@ -275,10 +275,16 @@ potentially_outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
275275/// Value is the number of PO dependencies of this AnalUnit.
276276/// Once this value drops to 0, the AnalUnit is a candidate for re-analysis.
277277outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
278/// This contains all `AnalUnit`s in `outdated` whose PO dependency count is 0.
278/// This is the set of all `AnalUnit`s in `outdated` whose PO dependency count is 0.
279279/// Such `AnalUnit`s are ready for immediate re-analysis.
280280/// See `findOutdatedToAnalyze` for details.
281outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty,
281outdated_ready: struct {
282 /// These are separate from other units because it allows `findOutdatedToAnalyze` to prioritize
283 /// functions, which is useful because it means they will be sent to codegen more quickly.
284 funcs: std.AutoArrayHashMapUnmanaged(InternPool.Index, void),
285 /// Does not contain `.func` units.
286 other: std.AutoArrayHashMapUnmanaged(AnalUnit, void),
287} = .{ .funcs = .empty, .other = .empty },
282288/// This contains a list of AnalUnit whose analysis or codegen failed, but the
283289/// failure was something like running out of disk space, and trying again may
284290/// succeed. On the next update, we will flush this list, marking all members of
......@@ -2835,7 +2841,8 @@ pub fn deinit(zcu: *Zcu) void {
28352841
28362842 zcu.potentially_outdated.deinit(gpa);
28372843 zcu.outdated.deinit(gpa);
2838 zcu.outdated_ready.deinit(gpa);
2844 zcu.outdated_ready.funcs.deinit(gpa);
2845 zcu.outdated_ready.other.deinit(gpa);
28392846 zcu.retryable_failures.deinit(gpa);
28402847
28412848 zcu.test_functions.deinit(gpa);
......@@ -3065,6 +3072,7 @@ pub fn markDependeeOutdated(
30653072 marked_po: enum { not_marked_po, marked_po },
30663073 dependee: InternPool.Dependee,
30673074) !void {
3075 const gpa = zcu.comp.gpa;
30683076 deps_log.debug("outdated dependee: {f}", .{zcu.fmtDependee(dependee)});
30693077 var it = zcu.intern_pool.dependencyIterator(dependee);
30703078 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);
......@@ -3078,7 +3086,10 @@ pub fn markDependeeOutdated(
30783086 deps_log.debug("outdated {f} => already outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });
30793087 if (po_dep_count.* == 0) {
30803088 deps_log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});
3081 try zcu.outdated_ready.put(zcu.gpa, depender, {});
3089 switch (depender.unwrap()) {
3090 .func => |func| try zcu.outdated_ready.funcs.put(gpa, func, {}),
3091 else => try zcu.outdated_ready.other.put(gpa, depender, {}),
3092 }
30823093 }
30833094 },
30843095 }
......@@ -3094,14 +3105,17 @@ pub fn markDependeeOutdated(
30943105 },
30953106 };
30963107 try zcu.outdated.putNoClobber(
3097 zcu.gpa,
3108 gpa,
30983109 depender,
30993110 new_po_dep_count,
31003111 );
31013112 deps_log.debug("outdated {f} => new outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), new_po_dep_count });
31023113 if (new_po_dep_count == 0) {
31033114 deps_log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});
3104 try zcu.outdated_ready.put(zcu.gpa, depender, {});
3115 switch (depender.unwrap()) {
3116 .func => |func| try zcu.outdated_ready.funcs.put(gpa, func, {}),
3117 else => try zcu.outdated_ready.other.put(gpa, depender, {}),
3118 }
31053119 }
31063120 // If this is a Decl and was not previously PO, we must recursively
31073121 // mark dependencies on its tyval as PO.
......@@ -3119,6 +3133,7 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
31193133}
31203134/// Assumes that `zcu.outdated_lock` is already held exclusively.
31213135fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {
3136 const gpa = zcu.comp.gpa;
31223137 deps_log.debug("up-to-date dependee: {f}", .{zcu.fmtDependee(dependee)});
31233138 var it = zcu.intern_pool.dependencyIterator(dependee);
31243139 while (it.next()) |depender| {
......@@ -3129,7 +3144,10 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {
31293144 deps_log.debug("up-to-date {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });
31303145 if (po_dep_count.* == 0) {
31313146 deps_log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});
3132 try zcu.outdated_ready.put(zcu.gpa, depender, {});
3147 switch (depender.unwrap()) {
3148 .func => |func| try zcu.outdated_ready.funcs.put(gpa, func, {}),
3149 else => try zcu.outdated_ready.other.put(gpa, depender, {}),
3150 }
31333151 }
31343152 continue;
31353153 }
......@@ -3167,7 +3185,8 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {
31673185/// in turn be PO, due to a dependency on the original AnalUnit's tyval or IES.
31683186///
31693187/// Assumes that `zcu.outdated_lock` is already held exclusively.
3170fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) !void {
3188fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) Allocator.Error!void {
3189 const gpa = zcu.comp.gpa;
31713190 const ip = &zcu.intern_pool;
31723191 const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) {
31733192 .@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies
......@@ -3181,10 +3200,12 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
31813200 var it = ip.dependencyIterator(dependee);
31823201 while (it.next()) |po| {
31833202 if (zcu.outdated.getPtr(po)) |po_dep_count| {
3184 // This dependency is already outdated, but it now has one more PO
3185 // dependency.
3203 // This dependency is already outdated, but it now has one more PO dependency.
31863204 if (po_dep_count.* == 0) {
3187 _ = zcu.outdated_ready.swapRemove(po);
3205 switch (po.unwrap()) {
3206 .func => |func| _ = zcu.outdated_ready.funcs.swapRemove(func),
3207 else => _ = zcu.outdated_ready.other.swapRemove(po),
3208 }
31883209 }
31893210 po_dep_count.* += 1;
31903211 deps_log.debug("po {f} => {f} [outdated] po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), po_dep_count.* });
......@@ -3196,7 +3217,7 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
31963217 deps_log.debug("po {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), n.* });
31973218 continue;
31983219 }
3199 try zcu.potentially_outdated.putNoClobber(zcu.gpa, po, 1);
3220 try zcu.potentially_outdated.putNoClobber(gpa, po, 1);
32003221 deps_log.debug("po {f} => {f} po_deps=1", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po) });
32013222 // This AnalUnit was not already PO, so we must recursively mark its dependers as also PO.
32023223 try zcu.markTransitiveDependersPotentiallyOutdated(po);
......@@ -3208,10 +3229,20 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
32083229/// recursive analysis (all of its previously-marked dependencies are already up-to-date), because
32093230/// recursive analysis can cause over-analysis on incremental updates.
32103231pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
3211 // MLUGG TODO: priorize `func` units, just like we used to do in the Compilation job queue.
3232 // We prioritize functions, because the sooner they get analyzed, the sooner they can be send to
3233 // the codegen backend and linker, which are usually running in parallel (so this can increase
3234 // parallelism).
3235 // TODO: perhaps we should also experiment with *avoiding* functions if the codegen/link queue
3236 // is backed up (for instance due to a very large function). That could help minimize blocking
3237 // on the main thread in `CodegenTaskPool.start` waiting for the linker to catch up.
3238 if (zcu.outdated_ready.funcs.count() > 0) {
3239 const unit: AnalUnit = .wrap(.{ .func = zcu.outdated_ready.funcs.keys()[0] });
3240 log.debug("findOutdatedToAnalyze: {f}", .{zcu.fmtAnalUnit(unit)});
3241 return unit;
3242 }
32123243
3213 if (zcu.outdated_ready.count() > 0) {
3214 const unit = zcu.outdated_ready.keys()[0];
3244 if (zcu.outdated_ready.other.count() > 0) {
3245 const unit = zcu.outdated_ready.other.keys()[0];
32153246 log.debug("findOutdatedToAnalyze: {f}", .{zcu.fmtAnalUnit(unit)});
32163247 return unit;
32173248 }
......@@ -3502,13 +3533,12 @@ pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func: InternPool.Index) !void {
35023533 assert(func == ip.unwrapCoercedFunc(func)); // analyze the body of the original function, not a coerced one
35033534 if (ip.setWantRuntimeFnAnalysis(io, func)) {
35043535 // This is the first reference to this function, so we must ensure it will be analyzed.
3505 const unit: AnalUnit = .wrap(.{ .func = func });
35063536 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);
35073537 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io);
35083538 try zcu.outdated.ensureUnusedCapacity(gpa, 1);
3509 try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
3510 zcu.outdated.putAssumeCapacityNoClobber(unit, 0);
3511 zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
3539 try zcu.outdated_ready.funcs.ensureUnusedCapacity(gpa, 1);
3540 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .func = func }), 0);
3541 zcu.outdated_ready.funcs.putAssumeCapacityNoClobber(func, {});
35123542 }
35133543}
35143544
......@@ -3522,11 +3552,11 @@ pub fn ensureNavValAnalysisQueued(zcu: *Zcu, nav: InternPool.Nav.Index) !void {
35223552 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);
35233553 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io);
35243554 try zcu.outdated.ensureUnusedCapacity(gpa, 2);
3525 try zcu.outdated_ready.ensureUnusedCapacity(gpa, 2);
3555 try zcu.outdated_ready.other.ensureUnusedCapacity(gpa, 2);
35263556 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .nav_val = nav }), 0);
35273557 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .nav_ty = nav }), 0);
3528 zcu.outdated_ready.putAssumeCapacityNoClobber(.wrap(.{ .nav_val = nav }), {});
3529 zcu.outdated_ready.putAssumeCapacityNoClobber(.wrap(.{ .nav_ty = nav }), {});
3558 zcu.outdated_ready.other.putAssumeCapacityNoClobber(.wrap(.{ .nav_val = nav }), {});
3559 zcu.outdated_ready.other.putAssumeCapacityNoClobber(.wrap(.{ .nav_ty = nav }), {});
35303560 }
35313561}
35323562
......@@ -3540,9 +3570,9 @@ pub fn queueComptimeUnitAnalysis(zcu: *Zcu, cu: InternPool.ComptimeUnit.Id) Allo
35403570 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(io);
35413571 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(io);
35423572 try zcu.outdated.ensureUnusedCapacity(gpa, 1);
3543 try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
3573 try zcu.outdated_ready.other.ensureUnusedCapacity(gpa, 1);
35443574 zcu.outdated.putAssumeCapacityNoClobber(unit, 0);
3545 zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
3575 zcu.outdated_ready.other.putAssumeCapacityNoClobber(unit, {});
35463576}
35473577
35483578/// If `unit` was marked as outdated or porentially outdated, clears that status and returns `true`.
......@@ -3552,7 +3582,15 @@ pub fn clearOutdatedState(zcu: *Zcu, unit: AnalUnit) bool {
35523582 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(io);
35533583 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(io);
35543584 if (zcu.outdated.fetchSwapRemove(unit)) |kv| {
3555 if (kv.value == 0) assert(zcu.outdated_ready.swapRemove(unit));
3585 const was_ready = switch (unit.unwrap()) {
3586 .func => |func| zcu.outdated_ready.funcs.swapRemove(func),
3587 else => zcu.outdated_ready.other.swapRemove(unit),
3588 };
3589 if (kv.value == 0) {
3590 assert(was_ready);
3591 } else {
3592 assert(!was_ready);
3593 }
35563594 return true;
35573595 } else if (zcu.potentially_outdated.swapRemove(unit)) {
35583596 return true;