Commit 075c103332

mlugg <mlugg@mlugg.co.uk>
2024-03-09 14:43:01
compiler: add `func_ies` incremental dependencies
This was an oversight in my original design. This new form of dependency is invalidated when the resolved IES for a runtime function changes.
1 parent 1421d32
src/InternPool.zig
@@ -67,6 +67,9 @@ src_hash_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index)
 /// Dependencies on the value of a Decl.
 /// Value is index into `dep_entries` of the first dependency on this Decl value.
 decl_val_deps: std.AutoArrayHashMapUnmanaged(DeclIndex, DepEntry.Index) = .{},
+/// Dependencies on the IES of a runtime function.
+/// Value is index into `dep_entries` of the first dependency on this Decl value.
+func_ies_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index) = .{},
 /// Dependencies on the full set of names in a ZIR namespace.
 /// Key refers to a `struct_decl`, `union_decl`, etc.
 /// Value is index into `dep_entries` of the first dependency on this namespace.
@@ -167,6 +170,7 @@ pub const Depender = enum(u32) {
 pub const Dependee = union(enum) {
     src_hash: TrackedInst.Index,
     decl_val: DeclIndex,
+    func_ies: Index,
     namespace: TrackedInst.Index,
     namespace_name: NamespaceNameKey,
 };
@@ -212,6 +216,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
     const first_entry = switch (dependee) {
         .src_hash => |x| ip.src_hash_deps.get(x),
         .decl_val => |x| ip.decl_val_deps.get(x),
+        .func_ies => |x| ip.func_ies_deps.get(x),
         .namespace => |x| ip.namespace_deps.get(x),
         .namespace_name => |x| ip.namespace_name_deps.get(x),
     } orelse return .{
@@ -251,6 +256,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: Depender, depend
             const gop = try switch (tag) {
                 .src_hash => ip.src_hash_deps,
                 .decl_val => ip.decl_val_deps,
+                .func_ies => ip.func_ies_deps,
                 .namespace => ip.namespace_deps,
                 .namespace_name => ip.namespace_name_deps,
             }.getOrPut(gpa, dependee_payload);
@@ -4324,6 +4330,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
 
     ip.src_hash_deps.deinit(gpa);
     ip.decl_val_deps.deinit(gpa);
+    ip.func_ies_deps.deinit(gpa);
     ip.namespace_deps.deinit(gpa);
     ip.namespace_name_deps.deinit(gpa);
 
src/Module.zig
@@ -2661,10 +2661,9 @@ pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void {
         }
         // If this is a Decl and was not previously PO, we must recursively
         // mark dependencies on its tyval as PO.
-        if (opt_po_entry == null) switch (depender.unwrap()) {
-            .decl => |decl_index| try zcu.markDeclDependenciesPotentiallyOutdated(decl_index),
-            .func => {},
-        };
+        if (opt_po_entry == null) {
+            try zcu.markTransitiveDependersPotentiallyOutdated(depender);
+        }
     }
 }
 
@@ -2694,15 +2693,19 @@ fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
         // as no longer PO.
         switch (depender.unwrap()) {
             .decl => |decl_index| try zcu.markPoDependeeUpToDate(.{ .decl_val = decl_index }),
-            .func => {},
+            .func => |func_index| try zcu.markPoDependeeUpToDate(.{ .func_ies = func_index }),
         }
     }
 }
 
-/// Given a Decl which is newly outdated or PO, mark all dependers which depend
-/// on its tyval as PO.
-fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !void {
-    var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index });
+/// Given a Depender which is newly outdated or PO, mark all Dependers which may
+/// in turn be PO, due to a dependency on the original Depender's tyval or IES.
+fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: InternPool.Depender) !void {
+    var it = zcu.intern_pool.dependencyIterator(switch (maybe_outdated.unwrap()) {
+        .decl => |decl_index| .{ .decl_val = decl_index }, // TODO: also `decl_ref` deps when introduced
+        .func => |func_index| .{ .func_ies = func_index },
+    });
+
     while (it.next()) |po| {
         if (zcu.outdated.getPtr(po)) |po_dep_count| {
             // This dependency is already outdated, but it now has one more PO
@@ -2719,14 +2722,9 @@ fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !v
             continue;
         }
         try zcu.potentially_outdated.putNoClobber(zcu.gpa, po, 1);
-        // If this ia a Decl, we must recursively mark dependencies
-        // on its tyval as PO.
-        switch (po.unwrap()) {
-            .decl => |po_decl| try zcu.markDeclDependenciesPotentiallyOutdated(po_decl),
-            .func => {},
-        }
+        // This Depender was not already PO, so we must recursively mark its dependers as also PO.
+        try zcu.markTransitiveDependersPotentiallyOutdated(po);
     }
-    // TODO: repeat the above for `decl_ty` dependencies when they are introduced
 }
 
 pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?InternPool.Depender {
@@ -2852,10 +2850,7 @@ pub fn flushRetryableFailures(zcu: *Zcu) !void {
         // This Depender was not marked PO, but is now outdated. Mark it as
         // such, then recursively mark transitive dependencies as PO.
         try zcu.outdated.put(gpa, depender, 0);
-        switch (depender.unwrap()) {
-            .decl => |decl| try zcu.markDeclDependenciesPotentiallyOutdated(decl),
-            .func => {},
-        }
+        try zcu.markTransitiveDependersPotentiallyOutdated(depender);
     }
     zcu.retryable_failures.clearRetainingCapacity();
 }
@@ -3142,11 +3137,19 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
     // decl now refers to a different function, making this one orphaned. If
     // that's the case, we should remove this function from the binary.
     if (decl.val.ip_index != func_index) {
+        try zcu.markDependeeOutdated(.{ .func_ies = func_index });
         ip.removeDependenciesForDepender(gpa, InternPool.Depender.wrap(.{ .func = func_index }));
         ip.remove(func_index);
         @panic("TODO: remove orphaned function from binary");
     }
 
+    // We'll want to remember what the IES used to be before the update for
+    // dependency invalidation purposes.
+    const old_resolved_ies = if (func.analysis(ip).inferred_error_set)
+        func.resolvedErrorSet(ip).*
+    else
+        .none;
+
     switch (decl.analysis) {
         .unreferenced => unreachable,
         .in_progress => unreachable,
@@ -3203,6 +3206,20 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
     };
     defer air.deinit(gpa);
 
+    const invalidate_ies_deps = i: {
+        if (!was_outdated) break :i false;
+        if (!func.analysis(ip).inferred_error_set) break :i true;
+        const new_resolved_ies = func.resolvedErrorSet(ip).*;
+        break :i new_resolved_ies != old_resolved_ies;
+    };
+    if (invalidate_ies_deps) {
+        log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)});
+        try zcu.markDependeeOutdated(.{ .func_ies = func_index });
+    } else if (was_outdated) {
+        log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)});
+        try zcu.markPoDependeeUpToDate(.{ .func_ies = func_index });
+    }
+
     const comp = zcu.comp;
 
     const dump_air = build_options.enable_debug_extensions and comp.verbose_air;
src/Sema.zig
@@ -36462,8 +36462,14 @@ fn resolveInferredErrorSet(
     const ip = &mod.intern_pool;
     const func_index = ip.iesFuncIndex(ies_index);
     const func = mod.funcInfo(func_index);
+
+    try sema.declareDependency(.{ .func_ies = func_index });
+
+    // TODO: during an incremental update this might not be `.none`, but the
+    // function might be out-of-date!
     const resolved_ty = func.resolvedErrorSet(ip).*;
     if (resolved_ty != .none) return resolved_ty;
+
     if (func.analysis(ip).state == .in_progress)
         return sema.fail(block, src, "unable to resolve inferred error set", .{});