Commit c6842b58d4

mlugg <mlugg@mlugg.co.uk>
2024-10-16 16:59:27
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`.
1 parent a7dd34b
Changed files (3)
src/Compilation.zig
@@ -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;
src/Sema.zig
@@ -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 });
src/Zcu.zig
@@ -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;