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.
This commit is contained in:
mlugg 2024-12-05 16:59:50 +00:00
parent 4d7818a76a
commit 7f3211a101
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
4 changed files with 81 additions and 31 deletions

View File

@ -3223,17 +3223,29 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
}
}
if (comp.zcu) |zcu| {
if (comp.incremental and bundle.root_list.items.len == 0) {
const should_have_error = for (zcu.transitive_failed_analysis.keys()) |failed_unit| {
const refs = try zcu.resolveReferences();
if (refs.contains(failed_unit)) break true;
} else false;
if (should_have_error) {
@panic("referenced transitive analysis errors, but none actually emitted");
// TODO: eventually, this should be behind `std.debug.runtime_safety`. But right now, this is a
// very common way for incremental compilation bugs to manifest, so let's always check it.
if (comp.zcu) |zcu| if (comp.incremental and bundle.root_list.items.len == 0) {
for (zcu.transitive_failed_analysis.keys()) |failed_unit| {
const refs = try zcu.resolveReferences();
var ref = refs.get(failed_unit) orelse continue;
// This AU is referenced and has a transitive compile error, meaning it referenced something with a compile error.
// However, we haven't reported any such error.
// This is a compiler bug.
const stderr = std.io.getStdErr().writer();
try stderr.writeAll("referenced transitive analysis errors, but none actually emitted\n");
try stderr.print("{} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)});
while (ref) |r| {
try stderr.print("referenced by: {}{s}\n", .{
zcu.fmtAnalUnit(r.referencer),
if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "",
});
ref = refs.get(r.referencer).?;
}
@panic("referenced transitive analysis errors, but none actually emitted");
}
}
};
const compile_log_text = if (comp.zcu) |m| m.compile_log_text.items else "";
return bundle.toOwnedBundle(compile_log_text);

View File

@ -8614,6 +8614,16 @@ pub fn getFuncDecl(
defer gop.deinit();
if (gop == .existing) {
extra.mutate.len = prev_extra_len;
const zir_body_inst_ptr = ip.funcDeclInfo(gop.existing).zirBodyInstPtr(ip);
if (zir_body_inst_ptr.* != key.zir_body_inst) {
// Since this function's `owner_nav` matches `key`, this *is* the function we're talking
// about. The only way it could have a different ZIR `func` instruction is if the old
// instruction has been lost and replaced with a new `TrackedInst.Index`.
assert(zir_body_inst_ptr.resolve(ip) == null);
zir_body_inst_ptr.* = key.zir_body_inst;
}
return gop.existing;
}
@ -8760,6 +8770,16 @@ pub fn getFuncDeclIes(
// An existing function type was found; undo the additions to our two arrays.
items.mutate.len -= 4;
extra.mutate.len = prev_extra_len;
const zir_body_inst_ptr = ip.funcDeclInfo(func_gop.existing).zirBodyInstPtr(ip);
if (zir_body_inst_ptr.* != key.zir_body_inst) {
// Since this function's `owner_nav` matches `key`, this *is* the function we're talking
// about. The only way it could have a different ZIR `func` instruction is if the old
// instruction has been lost and replaced with a new `TrackedInst.Index`.
assert(zir_body_inst_ptr.resolve(ip) == null);
zir_body_inst_ptr.* = key.zir_body_inst;
}
return func_gop.existing;
}
func_gop.putTentative(func_index);

View File

@ -538,7 +538,7 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
const cau = ip.getCau(cau_index);
log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)});
log.debug("ensureCauAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
assert(!zcu.analysis_in_progress.contains(anal_unit));
@ -576,13 +576,19 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
}
const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result|
.{ result, false }
// This `Cau` has gone from failed to success, so even if the value of the owner `Nav` didn't actually
// change, we need to invalidate the dependencies anyway.
.{ .{
.invalidate_decl_val = result.invalidate_decl_val or prev_failed,
.invalidate_decl_ref = result.invalidate_decl_ref or prev_failed,
}, false }
else |err| switch (err) {
error.AnalysisFail => res: {
if (!zcu.failed_analysis.contains(anal_unit)) {
// If this `Cau` caused the error, it would have an entry in `failed_analysis`.
// Since it does not, this must be a transitive failure.
try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
}
// We consider this `Cau` to be outdated if:
// * 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
// We only care about the uncoerced function.
const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
const anal_unit = AnalUnit.wrap(.{ .func = func_index });
log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
const func = zcu.funcInfo(maybe_coerced_func_index);
log.debug("ensureFuncBodyAnalyzed {d}", .{@intFromEnum(func_index)});
const anal_unit = AnalUnit.wrap(.{ .func = func_index });
const func_outdated = zcu.outdated.swapRemove(anal_unit) or
zcu.potentially_outdated.swapRemove(anal_unit);
@ -740,6 +746,7 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
// If this function caused the error, it would have an entry in `failed_analysis`.
// Since it does not, this must be a transitive failure.
try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
}
// We consider the IES to be outdated if the function previously succeeded analysis; in this case,
// 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
if (func_outdated) {
if (ies_outdated) {
log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)});
try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index });
} else {
log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)});
try zcu.markPoDependeeUpToDate(.{ .interned = func_index });
}
}
@ -779,6 +784,7 @@ fn ensureFuncBodyAnalyzedInner(
// results in the worst case.
if (func.generic_owner == .none) {
// Among another things, this ensures that the function's `zir_body_inst` is correct.
try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?);
if (ip.getNav(func.owner_nav).status.resolved.val != func_index) {
// This function is no longer referenced! There's no point in re-analyzing it.
@ -787,6 +793,7 @@ fn ensureFuncBodyAnalyzedInner(
}
} else {
const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;
// Among another things, this ensures that the function's `zir_body_inst` is correct.
try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?);
if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) {
// The generic owner is no longer referenced, so this function is also unreferenced.
@ -824,8 +831,8 @@ fn ensureFuncBodyAnalyzedInner(
}
}
log.debug("analyze and generate fn body '{d}'; reason='{s}'", .{
@intFromEnum(func_index),
log.debug("analyze and generate fn body {}; reason='{s}'", .{
zcu.fmtAnalUnit(anal_unit),
if (func_outdated) "outdated" else "never analyzed",
});
@ -1164,7 +1171,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
.none, .type => false,
};
log.debug("semaCau '{d}'", .{@intFromEnum(cau_index)});
log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)});
try zcu.analysis_in_progress.put(gpa, anal_unit, {});
errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
@ -2307,16 +2314,14 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err
return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, name));
}
/// Removes any entry from `Zcu.failed_files` associated with `file`. Acquires `Compilation.mutex` as needed.
/// `file.zir` must be unchanged from the last update, as it is used to determine if there is such an entry.
fn lockAndClearFileCompileError(pt: Zcu.PerThread, file: *Zcu.File) void {
switch (file.status) {
.success_zir, .retryable_failure => {},
.never_loaded, .parse_failure, .astgen_failure => {
pt.zcu.comp.mutex.lock();
defer pt.zcu.comp.mutex.unlock();
if (pt.zcu.failed_files.fetchSwapRemove(file)) |kv| {
if (kv.value) |msg| msg.destroy(pt.zcu.gpa); // Delete previous error message.
}
},
if (!file.zir_loaded or !file.zir.hasCompileErrors()) return;
pt.zcu.comp.mutex.lock();
defer pt.zcu.comp.mutex.unlock();
if (pt.zcu.failed_files.fetchSwapRemove(file)) |kv| {
if (kv.value) |msg| msg.destroy(pt.zcu.gpa); // Delete previous error message.
}
}

View File

@ -6096,11 +6096,18 @@ fn cmdAstCheck(
var error_bundle = try wip_errors.toOwnedBundle("");
defer error_bundle.deinit(gpa);
error_bundle.renderToStdErr(color.renderOptions());
process.exit(1);
if (file.zir.loweringFailed()) {
process.exit(1);
}
}
if (!want_output_text) {
return cleanExit();
if (file.zir.hasCompileErrors()) {
process.exit(1);
} else {
return cleanExit();
}
}
if (!build_options.enable_debug_extensions) {
fatal("-t option only available in builds of zig with debug extensions", .{});
@ -6144,7 +6151,13 @@ fn cmdAstCheck(
// zig fmt: on
}
return @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut());
try @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut());
if (file.zir.hasCompileErrors()) {
process.exit(1);
} else {
return cleanExit();
}
}
fn cmdDetectCpu(