Zcu: cache output of resolveReferences between calls

This not only simplifies the error bundling logic, but also improves
efficiency by allowing the result to be cached between, for instance,
multiple calls to `totalErrorCount`.
This commit is contained in:
mlugg 2024-10-16 15:59:27 +01:00
parent a7dd34bfc5
commit c6842b58d4
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
3 changed files with 52 additions and 38 deletions

View File

@ -3076,15 +3076,12 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
});
}
var all_references: ?std.AutoHashMapUnmanaged(InternPool.AnalUnit, ?Zcu.ResolvedReference) = null;
defer if (all_references) |*a| a.deinit(gpa);
if (comp.zcu) |zcu| {
const ip = &zcu.intern_pool;
for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| {
if (error_msg) |msg| {
try addModuleErrorMsg(zcu, &bundle, msg.*, &all_references);
try addModuleErrorMsg(zcu, &bundle, msg.*);
} else {
// Must be ZIR errors. Note that this may include AST errors.
// addZirErrorMessages asserts that the tree is loaded.
@ -3093,7 +3090,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
}
}
for (zcu.failed_embed_files.values()) |error_msg| {
try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
try addModuleErrorMsg(zcu, &bundle, error_msg.*);
}
{
const SortOrder = struct {
@ -3136,10 +3133,8 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
}
for (zcu.failed_analysis.keys(), zcu.failed_analysis.values()) |anal_unit, error_msg| {
if (comp.incremental) {
if (all_references == null) {
all_references = try zcu.resolveReferences();
}
if (!all_references.?.contains(anal_unit)) continue;
const refs = try zcu.resolveReferences();
if (!refs.contains(anal_unit)) continue;
}
const file_index = switch (anal_unit.unwrap()) {
@ -3151,7 +3146,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
// We'll try again once parsing succeeds.
if (!zcu.fileByIndex(file_index).okToReportErrors()) continue;
try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
try addModuleErrorMsg(zcu, &bundle, error_msg.*);
if (zcu.cimport_errors.get(anal_unit)) |errors| {
for (errors.getMessages()) |err_msg_index| {
const err_msg = errors.getErrorMessage(err_msg_index);
@ -3175,10 +3170,10 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
}
for (zcu.failed_codegen.keys(), zcu.failed_codegen.values()) |nav, error_msg| {
if (!zcu.navFileScope(nav).okToReportErrors()) continue;
try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
try addModuleErrorMsg(zcu, &bundle, error_msg.*);
}
for (zcu.failed_exports.values()) |value| {
try addModuleErrorMsg(zcu, &bundle, value.*, &all_references);
try addModuleErrorMsg(zcu, &bundle, value.*);
}
const actual_error_count = zcu.intern_pool.global_error_set.getNamesFromMainThread().len;
@ -3252,17 +3247,15 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
};
}
try addModuleErrorMsg(zcu, &bundle, err_msg, &all_references);
try addModuleErrorMsg(zcu, &bundle, err_msg);
}
}
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| {
if (all_references == null) {
all_references = try zcu.resolveReferences();
}
if (all_references.?.contains(failed_unit)) break true;
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");
@ -3331,14 +3324,13 @@ pub const ErrorNoteHashContext = struct {
};
pub fn addModuleErrorMsg(
mod: *Zcu,
zcu: *Zcu,
eb: *ErrorBundle.Wip,
module_err_msg: Zcu.ErrorMsg,
all_references: *?std.AutoHashMapUnmanaged(InternPool.AnalUnit, ?Zcu.ResolvedReference),
) !void {
const gpa = eb.gpa;
const ip = &mod.intern_pool;
const err_src_loc = module_err_msg.src_loc.upgrade(mod);
const ip = &zcu.intern_pool;
const err_src_loc = module_err_msg.src_loc.upgrade(zcu);
const err_source = err_src_loc.file_scope.getSource(gpa) catch |err| {
const file_path = try err_src_loc.file_scope.fullPath(gpa);
defer gpa.free(file_path);
@ -3358,22 +3350,20 @@ pub fn addModuleErrorMsg(
defer ref_traces.deinit(gpa);
if (module_err_msg.reference_trace_root.unwrap()) |rt_root| {
if (all_references.* == null) {
all_references.* = try mod.resolveReferences();
}
const all_references = try zcu.resolveReferences();
var seen: std.AutoHashMapUnmanaged(InternPool.AnalUnit, void) = .empty;
defer seen.deinit(gpa);
const max_references = mod.comp.reference_trace orelse Sema.default_reference_trace_len;
const max_references = zcu.comp.reference_trace orelse Sema.default_reference_trace_len;
var referenced_by = rt_root;
while (all_references.*.?.get(referenced_by)) |maybe_ref| {
while (all_references.get(referenced_by)) |maybe_ref| {
const ref = maybe_ref orelse break;
const gop = try seen.getOrPut(gpa, ref.referencer);
if (gop.found_existing) break;
if (ref_traces.items.len < max_references) {
const src = ref.src.upgrade(mod);
const src = ref.src.upgrade(zcu);
const source = try src.file_scope.getSource(gpa);
const span = try src.span(gpa);
const loc = std.zig.findLineColumn(source.bytes, span.main);
@ -3385,7 +3375,7 @@ pub fn addModuleErrorMsg(
.type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
.none => "comptime",
},
.func => |f| ip.getNav(mod.funcInfo(f).owner_nav).name.toSlice(ip),
.func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip),
};
try ref_traces.append(gpa, .{
.decl_name = try eb.addString(name),
@ -3435,7 +3425,7 @@ pub fn addModuleErrorMsg(
defer notes.deinit(gpa);
for (module_err_msg.notes) |module_note| {
const note_src_loc = module_note.src_loc.upgrade(mod);
const note_src_loc = module_note.src_loc.upgrade(zcu);
const source = try note_src_loc.file_scope.getSource(gpa);
const span = try note_src_loc.span(gpa);
const loc = std.zig.findLineColumn(source.bytes, span.main);
@ -3488,13 +3478,13 @@ pub fn performAllTheWork(
comp: *Compilation,
main_progress_node: std.Progress.Node,
) JobError!void {
defer if (comp.zcu) |mod| {
mod.sema_prog_node.end();
mod.sema_prog_node = std.Progress.Node.none;
mod.codegen_prog_node.end();
mod.codegen_prog_node = std.Progress.Node.none;
defer if (comp.zcu) |zcu| {
zcu.sema_prog_node.end();
zcu.sema_prog_node = std.Progress.Node.none;
zcu.codegen_prog_node.end();
zcu.codegen_prog_node = std.Progress.Node.none;
mod.generation += 1;
zcu.generation += 1;
};
try comp.performAllTheWorkInner(main_progress_node);
if (!InternPool.single_threaded) if (comp.codegen_work.job_error) |job_error| return job_error;

View File

@ -2559,10 +2559,9 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg
const zcu = sema.pt.zcu;
if (build_options.enable_debug_extensions and zcu.comp.debug_compile_errors) {
var all_references: ?std.AutoHashMapUnmanaged(AnalUnit, ?Zcu.ResolvedReference) = null;
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
wip_errors.init(gpa) catch @panic("out of memory");
Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, &all_references) catch @panic("out of memory");
Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*) catch @panic("out of memory");
std.debug.print("compile error during Sema:\n", .{});
var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
error_bundle.renderToStdErr(.{ .ttyconf = .no_color });

View File

@ -173,6 +173,10 @@ retryable_failures: std.ArrayListUnmanaged(AnalUnit) = .empty,
/// These are the modules which we initially queue for analysis in `Compilation.update`.
/// `resolveReferences` will use these as the root of its reachability traversal.
analysis_roots: std.BoundedArray(*Package.Module, 3) = .{},
/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and
/// reset to `null` when any semantic analysis occurs (since this invalidates the data).
/// Allocated into `gpa`.
resolved_references: ?std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) = null,
stage1_flags: packed struct {
have_winmain: bool = false,
@ -2192,6 +2196,8 @@ pub fn deinit(zcu: *Zcu) void {
zcu.all_type_references.deinit(gpa);
zcu.free_type_references.deinit(gpa);
if (zcu.resolved_references) |*r| r.deinit(gpa);
zcu.intern_pool.deinit(gpa);
}
@ -2760,6 +2766,8 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void {
pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void {
const gpa = zcu.gpa;
zcu.clearCachedResolvedReferences();
unit_refs: {
const kv = zcu.reference_table.fetchSwapRemove(anal_unit) orelse break :unit_refs;
var idx = kv.value;
@ -2792,6 +2800,8 @@ pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void {
pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit, ref_src: LazySrcLoc) Allocator.Error!void {
const gpa = zcu.gpa;
zcu.clearCachedResolvedReferences();
try zcu.reference_table.ensureUnusedCapacity(gpa, 1);
const ref_idx = zcu.free_references.popOrNull() orelse idx: {
@ -2815,6 +2825,8 @@ pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit
pub fn addTypeReference(zcu: *Zcu, src_unit: AnalUnit, referenced_type: InternPool.Index, ref_src: LazySrcLoc) Allocator.Error!void {
const gpa = zcu.gpa;
zcu.clearCachedResolvedReferences();
try zcu.type_reference_table.ensureUnusedCapacity(gpa, 1);
const ref_idx = zcu.free_type_references.popOrNull() orelse idx: {
@ -2835,6 +2847,11 @@ pub fn addTypeReference(zcu: *Zcu, src_unit: AnalUnit, referenced_type: InternPo
gop.value_ptr.* = @intCast(ref_idx);
}
fn clearCachedResolvedReferences(zcu: *Zcu) void {
if (zcu.resolved_references) |*r| r.deinit(zcu.gpa);
zcu.resolved_references = null;
}
pub fn errorSetBits(zcu: *const Zcu) u16 {
if (zcu.error_limit == 0) return 0;
return @as(u16, std.math.log2_int(ErrorInt, zcu.error_limit)) + 1;
@ -3138,7 +3155,15 @@ pub const ResolvedReference = struct {
/// Returns a mapping from an `AnalUnit` to where it is referenced.
/// If the value is `null`, the `AnalUnit` is a root of analysis.
/// If an `AnalUnit` is not in the returned map, it is unreferenced.
pub fn resolveReferences(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) {
/// The returned hashmap is owned by the `Zcu`, so should not be freed by the caller.
/// This hashmap is cached, so repeated calls to this function are cheap.
pub fn resolveReferences(zcu: *Zcu) !*const std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) {
if (zcu.resolved_references == null) {
zcu.resolved_references = try zcu.resolveReferencesInner();
}
return &zcu.resolved_references.?;
}
fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) {
const gpa = zcu.gpa;
const comp = zcu.comp;
const ip = &zcu.intern_pool;