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,...@@ -275,10 +275,16 @@ potentially_outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
275/// Value is the number of PO dependencies of this AnalUnit.275/// Value is the number of PO dependencies of this AnalUnit.
276/// Once this value drops to 0, the AnalUnit is a candidate for re-analysis.276/// Once this value drops to 0, the AnalUnit is a candidate for re-analysis.
277outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,277outdated: 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.
279/// Such `AnalUnit`s are ready for immediate re-analysis.279/// Such `AnalUnit`s are ready for immediate re-analysis.
280/// See `findOutdatedToAnalyze` for details.280/// 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 },
282/// This contains a list of AnalUnit whose analysis or codegen failed, but the288/// This contains a list of AnalUnit whose analysis or codegen failed, but the
283/// failure was something like running out of disk space, and trying again may289/// failure was something like running out of disk space, and trying again may
284/// succeed. On the next update, we will flush this list, marking all members of290/// succeed. On the next update, we will flush this list, marking all members of
...@@ -2835,7 +2841,8 @@ pub fn deinit(zcu: *Zcu) void {...@@ -2835,7 +2841,8 @@ pub fn deinit(zcu: *Zcu) void {
28352841
2836 zcu.potentially_outdated.deinit(gpa);2842 zcu.potentially_outdated.deinit(gpa);
2837 zcu.outdated.deinit(gpa);2843 zcu.outdated.deinit(gpa);
2838 zcu.outdated_ready.deinit(gpa);2844 zcu.outdated_ready.funcs.deinit(gpa);
2845 zcu.outdated_ready.other.deinit(gpa);
2839 zcu.retryable_failures.deinit(gpa);2846 zcu.retryable_failures.deinit(gpa);
28402847
2841 zcu.test_functions.deinit(gpa);2848 zcu.test_functions.deinit(gpa);
...@@ -3065,6 +3072,7 @@ pub fn markDependeeOutdated(...@@ -3065,6 +3072,7 @@ pub fn markDependeeOutdated(
3065 marked_po: enum { not_marked_po, marked_po },3072 marked_po: enum { not_marked_po, marked_po },
3066 dependee: InternPool.Dependee,3073 dependee: InternPool.Dependee,
3067) !void {3074) !void {
3075 const gpa = zcu.comp.gpa;
3068 deps_log.debug("outdated dependee: {f}", .{zcu.fmtDependee(dependee)});3076 deps_log.debug("outdated dependee: {f}", .{zcu.fmtDependee(dependee)});
3069 var it = zcu.intern_pool.dependencyIterator(dependee);3077 var it = zcu.intern_pool.dependencyIterator(dependee);
3070 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);3078 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);
...@@ -3078,7 +3086,10 @@ pub fn markDependeeOutdated(...@@ -3078,7 +3086,10 @@ pub fn markDependeeOutdated(
3078 deps_log.debug("outdated {f} => already outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });3086 deps_log.debug("outdated {f} => already outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });
3079 if (po_dep_count.* == 0) {3087 if (po_dep_count.* == 0) {
3080 deps_log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});3088 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 }
3082 }3093 }
3083 },3094 },
3084 }3095 }
...@@ -3094,14 +3105,17 @@ pub fn markDependeeOutdated(...@@ -3094,14 +3105,17 @@ pub fn markDependeeOutdated(
3094 },3105 },
3095 };3106 };
3096 try zcu.outdated.putNoClobber(3107 try zcu.outdated.putNoClobber(
3097 zcu.gpa,3108 gpa,
3098 depender,3109 depender,
3099 new_po_dep_count,3110 new_po_dep_count,
3100 );3111 );
3101 deps_log.debug("outdated {f} => new outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), new_po_dep_count });3112 deps_log.debug("outdated {f} => new outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), new_po_dep_count });
3102 if (new_po_dep_count == 0) {3113 if (new_po_dep_count == 0) {
3103 deps_log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});3114 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 }
3105 }3119 }
3106 // If this is a Decl and was not previously PO, we must recursively3120 // If this is a Decl and was not previously PO, we must recursively
3107 // mark dependencies on its tyval as PO.3121 // mark dependencies on its tyval as PO.
...@@ -3119,6 +3133,7 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {...@@ -3119,6 +3133,7 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
3119}3133}
3120/// Assumes that `zcu.outdated_lock` is already held exclusively.3134/// Assumes that `zcu.outdated_lock` is already held exclusively.
3121fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {3135fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {
3136 const gpa = zcu.comp.gpa;
3122 deps_log.debug("up-to-date dependee: {f}", .{zcu.fmtDependee(dependee)});3137 deps_log.debug("up-to-date dependee: {f}", .{zcu.fmtDependee(dependee)});
3123 var it = zcu.intern_pool.dependencyIterator(dependee);3138 var it = zcu.intern_pool.dependencyIterator(dependee);
3124 while (it.next()) |depender| {3139 while (it.next()) |depender| {
...@@ -3129,7 +3144,10 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {...@@ -3129,7 +3144,10 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {
3129 deps_log.debug("up-to-date {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });3144 deps_log.debug("up-to-date {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });
3130 if (po_dep_count.* == 0) {3145 if (po_dep_count.* == 0) {
3131 deps_log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});3146 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 }
3133 }3151 }
3134 continue;3152 continue;
3135 }3153 }
...@@ -3167,7 +3185,8 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {...@@ -3167,7 +3185,8 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {
3167/// in turn be PO, due to a dependency on the original AnalUnit's tyval or IES.3185/// in turn be PO, due to a dependency on the original AnalUnit's tyval or IES.
3168///3186///
3169/// Assumes that `zcu.outdated_lock` is already held exclusively.3187/// 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;
3171 const ip = &zcu.intern_pool;3190 const ip = &zcu.intern_pool;
3172 const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) {3191 const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) {
3173 .@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies3192 .@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies
...@@ -3181,10 +3200,12 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni...@@ -3181,10 +3200,12 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
3181 var it = ip.dependencyIterator(dependee);3200 var it = ip.dependencyIterator(dependee);
3182 while (it.next()) |po| {3201 while (it.next()) |po| {
3183 if (zcu.outdated.getPtr(po)) |po_dep_count| {3202 if (zcu.outdated.getPtr(po)) |po_dep_count| {
3184 // This dependency is already outdated, but it now has one more PO3203 // This dependency is already outdated, but it now has one more PO dependency.
3185 // dependency.
3186 if (po_dep_count.* == 0) {3204 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 }
3188 }3209 }
3189 po_dep_count.* += 1;3210 po_dep_count.* += 1;
3190 deps_log.debug("po {f} => {f} [outdated] po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), po_dep_count.* });3211 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...@@ -3196,7 +3217,7 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
3196 deps_log.debug("po {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), n.* });3217 deps_log.debug("po {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), n.* });
3197 continue;3218 continue;
3198 }3219 }
3199 try zcu.potentially_outdated.putNoClobber(zcu.gpa, po, 1);3220 try zcu.potentially_outdated.putNoClobber(gpa, po, 1);
3200 deps_log.debug("po {f} => {f} po_deps=1", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po) });3221 deps_log.debug("po {f} => {f} po_deps=1", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po) });
3201 // This AnalUnit was not already PO, so we must recursively mark its dependers as also PO.3222 // This AnalUnit was not already PO, so we must recursively mark its dependers as also PO.
3202 try zcu.markTransitiveDependersPotentiallyOutdated(po);3223 try zcu.markTransitiveDependersPotentiallyOutdated(po);
...@@ -3208,10 +3229,20 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni...@@ -3208,10 +3229,20 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
3208/// recursive analysis (all of its previously-marked dependencies are already up-to-date), because3229/// recursive analysis (all of its previously-marked dependencies are already up-to-date), because
3209/// recursive analysis can cause over-analysis on incremental updates.3230/// recursive analysis can cause over-analysis on incremental updates.
3210pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {3231pub 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) {3244 if (zcu.outdated_ready.other.count() > 0) {
3214 const unit = zcu.outdated_ready.keys()[0];3245 const unit = zcu.outdated_ready.other.keys()[0];
3215 log.debug("findOutdatedToAnalyze: {f}", .{zcu.fmtAnalUnit(unit)});3246 log.debug("findOutdatedToAnalyze: {f}", .{zcu.fmtAnalUnit(unit)});
3216 return unit;3247 return unit;
3217 }3248 }
...@@ -3502,13 +3533,12 @@ pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func: InternPool.Index) !void {...@@ -3502,13 +3533,12 @@ pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func: InternPool.Index) !void {
3502 assert(func == ip.unwrapCoercedFunc(func)); // analyze the body of the original function, not a coerced one3533 assert(func == ip.unwrapCoercedFunc(func)); // analyze the body of the original function, not a coerced one
3503 if (ip.setWantRuntimeFnAnalysis(io, func)) {3534 if (ip.setWantRuntimeFnAnalysis(io, func)) {
3504 // This is the first reference to this function, so we must ensure it will be analyzed.3535 // This is the first reference to this function, so we must ensure it will be analyzed.
3505 const unit: AnalUnit = .wrap(.{ .func = func });
3506 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);3536 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);
3507 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io);3537 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io);
3508 try zcu.outdated.ensureUnusedCapacity(gpa, 1);3538 try zcu.outdated.ensureUnusedCapacity(gpa, 1);
3509 try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);3539 try zcu.outdated_ready.funcs.ensureUnusedCapacity(gpa, 1);
3510 zcu.outdated.putAssumeCapacityNoClobber(unit, 0);3540 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .func = func }), 0);
3511 zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});3541 zcu.outdated_ready.funcs.putAssumeCapacityNoClobber(func, {});
3512 }3542 }
3513}3543}
35143544
...@@ -3522,11 +3552,11 @@ pub fn ensureNavValAnalysisQueued(zcu: *Zcu, nav: InternPool.Nav.Index) !void {...@@ -3522,11 +3552,11 @@ pub fn ensureNavValAnalysisQueued(zcu: *Zcu, nav: InternPool.Nav.Index) !void {
3522 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);3552 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);
3523 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io);3553 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io);
3524 try zcu.outdated.ensureUnusedCapacity(gpa, 2);3554 try zcu.outdated.ensureUnusedCapacity(gpa, 2);
3525 try zcu.outdated_ready.ensureUnusedCapacity(gpa, 2);3555 try zcu.outdated_ready.other.ensureUnusedCapacity(gpa, 2);
3526 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .nav_val = nav }), 0);3556 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .nav_val = nav }), 0);
3527 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .nav_ty = nav }), 0);3557 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .nav_ty = nav }), 0);
3528 zcu.outdated_ready.putAssumeCapacityNoClobber(.wrap(.{ .nav_val = nav }), {});3558 zcu.outdated_ready.other.putAssumeCapacityNoClobber(.wrap(.{ .nav_val = nav }), {});
3529 zcu.outdated_ready.putAssumeCapacityNoClobber(.wrap(.{ .nav_ty = nav }), {});3559 zcu.outdated_ready.other.putAssumeCapacityNoClobber(.wrap(.{ .nav_ty = nav }), {});
3530 }3560 }
3531}3561}
35323562
...@@ -3540,9 +3570,9 @@ pub fn queueComptimeUnitAnalysis(zcu: *Zcu, cu: InternPool.ComptimeUnit.Id) Allo...@@ -3540,9 +3570,9 @@ pub fn queueComptimeUnitAnalysis(zcu: *Zcu, cu: InternPool.ComptimeUnit.Id) Allo
3540 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(io);3570 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(io);
3541 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(io);3571 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(io);
3542 try zcu.outdated.ensureUnusedCapacity(gpa, 1);3572 try zcu.outdated.ensureUnusedCapacity(gpa, 1);
3543 try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);3573 try zcu.outdated_ready.other.ensureUnusedCapacity(gpa, 1);
3544 zcu.outdated.putAssumeCapacityNoClobber(unit, 0);3574 zcu.outdated.putAssumeCapacityNoClobber(unit, 0);
3545 zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});3575 zcu.outdated_ready.other.putAssumeCapacityNoClobber(unit, {});
3546}3576}
35473577
3548/// If `unit` was marked as outdated or porentially outdated, clears that status and returns `true`.3578/// 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 {...@@ -3552,7 +3582,15 @@ pub fn clearOutdatedState(zcu: *Zcu, unit: AnalUnit) bool {
3552 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(io);3582 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(io);
3553 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(io);3583 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(io);
3554 if (zcu.outdated.fetchSwapRemove(unit)) |kv| {3584 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 }
3556 return true;3594 return true;
3557 } else if (zcu.potentially_outdated.swapRemove(unit)) {3595 } else if (zcu.potentially_outdated.swapRemove(unit)) {
3558 return true;3596 return true;