authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-12-05 16:59:50+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-12-05 19:58:42+00:00
log7f3211a101d8763ec5f0009b219f6819dba2cd35
treee9bb31b31381f6fe07bf9d2253c4d7d5b56e882f
parent4d7818a76ad951f0a16c3831b31841430b1368f7
signaturelock-open Commit is signed but in an unrecognized format.

compiler: incremental compilation fixes

The previous commit exposed some bugs in incremental compilation. This commit fixes those, and adds a little more logging for debugging incremental compilation. Also, allow `ast-check -t` to dump ZIR when there are non-fatal AstGen errors.

4 files changed, 81 insertions(+), 31 deletions(-)

src/Compilation.zig+21-9
...@@ -3223,17 +3223,29 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {...@@ -3223,17 +3223,29 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
3223 }3223 }
3224 }3224 }
32253225
3226 if (comp.zcu) |zcu| {3226 // TODO: eventually, this should be behind `std.debug.runtime_safety`. But right now, this is a
3227 if (comp.incremental and bundle.root_list.items.len == 0) {3227 // very common way for incremental compilation bugs to manifest, so let's always check it.
3228 const should_have_error = for (zcu.transitive_failed_analysis.keys()) |failed_unit| {3228 if (comp.zcu) |zcu| if (comp.incremental and bundle.root_list.items.len == 0) {
3229 const refs = try zcu.resolveReferences();3229 for (zcu.transitive_failed_analysis.keys()) |failed_unit| {
3230 if (refs.contains(failed_unit)) break true;3230 const refs = try zcu.resolveReferences();
3231 } else false;3231 var ref = refs.get(failed_unit) orelse continue;
3232 if (should_have_error) {3232 // This AU is referenced and has a transitive compile error, meaning it referenced something with a compile error.
3233 @panic("referenced transitive analysis errors, but none actually emitted");3233 // However, we haven't reported any such error.
3234 // This is a compiler bug.
3235 const stderr = std.io.getStdErr().writer();
3236 try stderr.writeAll("referenced transitive analysis errors, but none actually emitted\n");
3237 try stderr.print("{} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)});
3238 while (ref) |r| {
3239 try stderr.print("referenced by: {}{s}\n", .{
3240 zcu.fmtAnalUnit(r.referencer),
3241 if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "",
3242 });
3243 ref = refs.get(r.referencer).?;
3234 }3244 }
3245
3246 @panic("referenced transitive analysis errors, but none actually emitted");
3235 }3247 }
3236 }3248 };
32373249
3238 const compile_log_text = if (comp.zcu) |m| m.compile_log_text.items else "";3250 const compile_log_text = if (comp.zcu) |m| m.compile_log_text.items else "";
3239 return bundle.toOwnedBundle(compile_log_text);3251 return bundle.toOwnedBundle(compile_log_text);
src/InternPool.zig+20
...@@ -8614,6 +8614,16 @@ pub fn getFuncDecl(...@@ -8614,6 +8614,16 @@ pub fn getFuncDecl(
8614 defer gop.deinit();8614 defer gop.deinit();
8615 if (gop == .existing) {8615 if (gop == .existing) {
8616 extra.mutate.len = prev_extra_len;8616 extra.mutate.len = prev_extra_len;
8617
8618 const zir_body_inst_ptr = ip.funcDeclInfo(gop.existing).zirBodyInstPtr(ip);
8619 if (zir_body_inst_ptr.* != key.zir_body_inst) {
8620 // Since this function's `owner_nav` matches `key`, this *is* the function we're talking
8621 // about. The only way it could have a different ZIR `func` instruction is if the old
8622 // instruction has been lost and replaced with a new `TrackedInst.Index`.
8623 assert(zir_body_inst_ptr.resolve(ip) == null);
8624 zir_body_inst_ptr.* = key.zir_body_inst;
8625 }
8626
8617 return gop.existing;8627 return gop.existing;
8618 }8628 }
86198629
...@@ -8760,6 +8770,16 @@ pub fn getFuncDeclIes(...@@ -8760,6 +8770,16 @@ pub fn getFuncDeclIes(
8760 // An existing function type was found; undo the additions to our two arrays.8770 // An existing function type was found; undo the additions to our two arrays.
8761 items.mutate.len -= 4;8771 items.mutate.len -= 4;
8762 extra.mutate.len = prev_extra_len;8772 extra.mutate.len = prev_extra_len;
8773
8774 const zir_body_inst_ptr = ip.funcDeclInfo(func_gop.existing).zirBodyInstPtr(ip);
8775 if (zir_body_inst_ptr.* != key.zir_body_inst) {
8776 // Since this function's `owner_nav` matches `key`, this *is* the function we're talking
8777 // about. The only way it could have a different ZIR `func` instruction is if the old
8778 // instruction has been lost and replaced with a new `TrackedInst.Index`.
8779 assert(zir_body_inst_ptr.resolve(ip) == null);
8780 zir_body_inst_ptr.* = key.zir_body_inst;
8781 }
8782
8763 return func_gop.existing;8783 return func_gop.existing;
8764 }8784 }
8765 func_gop.putTentative(func_index);8785 func_gop.putTentative(func_index);
src/Zcu/PerThread.zig+24-19
...@@ -538,7 +538,7 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu...@@ -538,7 +538,7 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
538 const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });538 const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
539 const cau = ip.getCau(cau_index);539 const cau = ip.getCau(cau_index);
540540
541 log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)});541 log.debug("ensureCauAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
542542
543 assert(!zcu.analysis_in_progress.contains(anal_unit));543 assert(!zcu.analysis_in_progress.contains(anal_unit));
544544
...@@ -576,13 +576,19 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu...@@ -576,13 +576,19 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
576 }576 }
577577
578 const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result|578 const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result|
579 .{ result, false }579 // This `Cau` has gone from failed to success, so even if the value of the owner `Nav` didn't actually
580 // change, we need to invalidate the dependencies anyway.
581 .{ .{
582 .invalidate_decl_val = result.invalidate_decl_val or prev_failed,
583 .invalidate_decl_ref = result.invalidate_decl_ref or prev_failed,
584 }, false }
580 else |err| switch (err) {585 else |err| switch (err) {
581 error.AnalysisFail => res: {586 error.AnalysisFail => res: {
582 if (!zcu.failed_analysis.contains(anal_unit)) {587 if (!zcu.failed_analysis.contains(anal_unit)) {
583 // If this `Cau` caused the error, it would have an entry in `failed_analysis`.588 // If this `Cau` caused the error, it would have an entry in `failed_analysis`.
584 // Since it does not, this must be a transitive failure.589 // Since it does not, this must be a transitive failure.
585 try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});590 try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
591 log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
586 }592 }
587 // We consider this `Cau` to be outdated if:593 // We consider this `Cau` to be outdated if:
588 // * Previous analysis succeeded; in this case, we need to re-analyze dependants to ensure594 // * Previous analysis succeeded; in this case, we need to re-analyze dependants to ensure
...@@ -707,12 +713,12 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter...@@ -707,12 +713,12 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
707713
708 // We only care about the uncoerced function.714 // We only care about the uncoerced function.
709 const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);715 const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
716 const anal_unit = AnalUnit.wrap(.{ .func = func_index });
710717
711 const func = zcu.funcInfo(maybe_coerced_func_index);718 log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
712719
713 log.debug("ensureFuncBodyAnalyzed {d}", .{@intFromEnum(func_index)});720 const func = zcu.funcInfo(maybe_coerced_func_index);
714721
715 const anal_unit = AnalUnit.wrap(.{ .func = func_index });
716 const func_outdated = zcu.outdated.swapRemove(anal_unit) or722 const func_outdated = zcu.outdated.swapRemove(anal_unit) or
717 zcu.potentially_outdated.swapRemove(anal_unit);723 zcu.potentially_outdated.swapRemove(anal_unit);
718724
...@@ -740,6 +746,7 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter...@@ -740,6 +746,7 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
740 // If this function caused the error, it would have an entry in `failed_analysis`.746 // If this function caused the error, it would have an entry in `failed_analysis`.
741 // Since it does not, this must be a transitive failure.747 // Since it does not, this must be a transitive failure.
742 try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});748 try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
749 log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
743 }750 }
744 // We consider the IES to be outdated if the function previously succeeded analysis; in this case,751 // We consider the IES to be outdated if the function previously succeeded analysis; in this case,
745 // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting752 // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting
...@@ -751,10 +758,8 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter...@@ -751,10 +758,8 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
751758
752 if (func_outdated) {759 if (func_outdated) {
753 if (ies_outdated) {760 if (ies_outdated) {
754 log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)});
755 try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index });761 try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index });
756 } else {762 } else {
757 log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)});
758 try zcu.markPoDependeeUpToDate(.{ .interned = func_index });763 try zcu.markPoDependeeUpToDate(.{ .interned = func_index });
759 }764 }
760 }765 }
...@@ -779,6 +784,7 @@ fn ensureFuncBodyAnalyzedInner(...@@ -779,6 +784,7 @@ fn ensureFuncBodyAnalyzedInner(
779 // results in the worst case.784 // results in the worst case.
780785
781 if (func.generic_owner == .none) {786 if (func.generic_owner == .none) {
787 // Among another things, this ensures that the function's `zir_body_inst` is correct.
782 try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?);788 try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?);
783 if (ip.getNav(func.owner_nav).status.resolved.val != func_index) {789 if (ip.getNav(func.owner_nav).status.resolved.val != func_index) {
784 // This function is no longer referenced! There's no point in re-analyzing it.790 // This function is no longer referenced! There's no point in re-analyzing it.
...@@ -787,6 +793,7 @@ fn ensureFuncBodyAnalyzedInner(...@@ -787,6 +793,7 @@ fn ensureFuncBodyAnalyzedInner(
787 }793 }
788 } else {794 } else {
789 const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;795 const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;
796 // Among another things, this ensures that the function's `zir_body_inst` is correct.
790 try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?);797 try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?);
791 if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) {798 if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) {
792 // The generic owner is no longer referenced, so this function is also unreferenced.799 // The generic owner is no longer referenced, so this function is also unreferenced.
...@@ -824,8 +831,8 @@ fn ensureFuncBodyAnalyzedInner(...@@ -824,8 +831,8 @@ fn ensureFuncBodyAnalyzedInner(
824 }831 }
825 }832 }
826833
827 log.debug("analyze and generate fn body '{d}'; reason='{s}'", .{834 log.debug("analyze and generate fn body {}; reason='{s}'", .{
828 @intFromEnum(func_index),835 zcu.fmtAnalUnit(anal_unit),
829 if (func_outdated) "outdated" else "never analyzed",836 if (func_outdated) "outdated" else "never analyzed",
830 });837 });
831838
...@@ -1164,7 +1171,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {...@@ -1164,7 +1171,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
1164 .none, .type => false,1171 .none, .type => false,
1165 };1172 };
11661173
1167 log.debug("semaCau '{d}'", .{@intFromEnum(cau_index)});1174 log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)});
11681175
1169 try zcu.analysis_in_progress.put(gpa, anal_unit, {});1176 try zcu.analysis_in_progress.put(gpa, anal_unit, {});
1170 errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);1177 errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
...@@ -2307,16 +2314,14 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err...@@ -2307,16 +2314,14 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err
2307 return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, name));2314 return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, name));
2308}2315}
23092316
2317/// Removes any entry from `Zcu.failed_files` associated with `file`. Acquires `Compilation.mutex` as needed.
2318/// `file.zir` must be unchanged from the last update, as it is used to determine if there is such an entry.
2310fn lockAndClearFileCompileError(pt: Zcu.PerThread, file: *Zcu.File) void {2319fn lockAndClearFileCompileError(pt: Zcu.PerThread, file: *Zcu.File) void {
2311 switch (file.status) {2320 if (!file.zir_loaded or !file.zir.hasCompileErrors()) return;
2312 .success_zir, .retryable_failure => {},2321 pt.zcu.comp.mutex.lock();
2313 .never_loaded, .parse_failure, .astgen_failure => {2322 defer pt.zcu.comp.mutex.unlock();
2314 pt.zcu.comp.mutex.lock();2323 if (pt.zcu.failed_files.fetchSwapRemove(file)) |kv| {
2315 defer pt.zcu.comp.mutex.unlock();2324 if (kv.value) |msg| msg.destroy(pt.zcu.gpa); // Delete previous error message.
2316 if (pt.zcu.failed_files.fetchSwapRemove(file)) |kv| {
2317 if (kv.value) |msg| msg.destroy(pt.zcu.gpa); // Delete previous error message.
2318 }
2319 },
2320 }2325 }
2321}2326}
23222327
src/main.zig+16-3
...@@ -6096,11 +6096,18 @@ fn cmdAstCheck(...@@ -6096,11 +6096,18 @@ fn cmdAstCheck(
6096 var error_bundle = try wip_errors.toOwnedBundle("");6096 var error_bundle = try wip_errors.toOwnedBundle("");
6097 defer error_bundle.deinit(gpa);6097 defer error_bundle.deinit(gpa);
6098 error_bundle.renderToStdErr(color.renderOptions());6098 error_bundle.renderToStdErr(color.renderOptions());
6099 process.exit(1);6099
6100 if (file.zir.loweringFailed()) {
6101 process.exit(1);
6102 }
6100 }6103 }
61016104
6102 if (!want_output_text) {6105 if (!want_output_text) {
6103 return cleanExit();6106 if (file.zir.hasCompileErrors()) {
6107 process.exit(1);
6108 } else {
6109 return cleanExit();
6110 }
6104 }6111 }
6105 if (!build_options.enable_debug_extensions) {6112 if (!build_options.enable_debug_extensions) {
6106 fatal("-t option only available in builds of zig with debug extensions", .{});6113 fatal("-t option only available in builds of zig with debug extensions", .{});
...@@ -6144,7 +6151,13 @@ fn cmdAstCheck(...@@ -6144,7 +6151,13 @@ fn cmdAstCheck(
6144 // zig fmt: on6151 // zig fmt: on
6145 }6152 }
61466153
6147 return @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut());6154 try @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut());
6155
6156 if (file.zir.hasCompileErrors()) {
6157 process.exit(1);
6158 } else {
6159 return cleanExit();
6160 }
6148}6161}
61496162
6150fn cmdDetectCpu(6163fn cmdDetectCpu(