Commit fac120bc3a

mlugg <mlugg@mlugg.co.uk>
2023-04-19 18:40:29
Module: mark function body dependencies, don't re-analyze anonymous decls
1 parent d5f1a88
Changed files (2)
src/Module.zig
@@ -564,7 +564,23 @@ pub const Decl = struct {
         }
     };
 
-    pub const DepsTable = std.AutoArrayHashMapUnmanaged(Decl.Index, void);
+    pub const DepsTable = std.AutoArrayHashMapUnmanaged(Decl.Index, DepType);
+
+    /// Later types take priority; e.g. if a dependent decl has both `normal`
+    /// and `function_body` dependencies on another decl, it will be marked as
+    /// having a `function_body` dependency.
+    pub const DepType = enum {
+        /// The dependent references or uses the dependency's value, so must be
+        /// updated whenever it is changed. However, if the dependency is a
+        /// function and its type is unchanged, the dependent does not need to
+        /// be updated.
+        normal,
+        /// The dependent performs an inline or comptime call to the dependency,
+        /// or is a generic instantiation of it. It must therefore be updated
+        /// whenever the dependency is updated, even if the function type
+        /// remained the same.
+        function_body,
+    };
 
     pub fn clearName(decl: *Decl, gpa: Allocator) void {
         gpa.free(mem.sliceTo(decl.name, 0));
@@ -4155,53 +4171,63 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
     decl_prog_node.activate();
     defer decl_prog_node.end();
 
-    const type_changed = mod.semaDecl(decl_index) catch |err| switch (err) {
-        error.AnalysisFail => {
-            if (decl.analysis == .in_progress) {
-                // If this decl caused the compile error, the analysis field would
-                // be changed to indicate it was this Decl's fault. Because this
-                // did not happen, we infer here that it was a dependency failure.
-                decl.analysis = .dependency_failure;
-            }
-            return error.AnalysisFail;
-        },
-        error.NeededSourceLocation => unreachable,
-        error.GenericPoison => unreachable,
-        else => |e| {
-            decl.analysis = .sema_failure_retryable;
-            try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
-            mod.failed_decls.putAssumeCapacityNoClobber(decl_index, try ErrorMsg.create(
-                mod.gpa,
-                decl.srcLoc(),
-                "unable to analyze: {s}",
-                .{@errorName(e)},
-            ));
-            return error.AnalysisFail;
-        },
+    const type_changed = blk: {
+        if (decl.zir_decl_index == 0 and !mod.declIsRoot(decl_index)) {
+            // Anonymous decl. We don't semantically analyze these.
+            break :blk false; // tv unchanged
+        }
+
+        break :blk mod.semaDecl(decl_index) catch |err| switch (err) {
+            error.AnalysisFail => {
+                if (decl.analysis == .in_progress) {
+                    // If this decl caused the compile error, the analysis field would
+                    // be changed to indicate it was this Decl's fault. Because this
+                    // did not happen, we infer here that it was a dependency failure.
+                    decl.analysis = .dependency_failure;
+                }
+                return error.AnalysisFail;
+            },
+            error.NeededSourceLocation => unreachable,
+            error.GenericPoison => unreachable,
+            else => |e| {
+                decl.analysis = .sema_failure_retryable;
+                try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
+                mod.failed_decls.putAssumeCapacityNoClobber(decl_index, try ErrorMsg.create(
+                    mod.gpa,
+                    decl.srcLoc(),
+                    "unable to analyze: {s}",
+                    .{@errorName(e)},
+                ));
+                return error.AnalysisFail;
+            },
+        };
     };
 
     if (subsequent_analysis) {
-        // We may need to chase the dependants and re-analyze them.
-        // However, if the decl is a function, and the type is the same, we do not need to.
-        if (type_changed or decl.ty.zigTypeTag() != .Fn) {
-            for (decl.dependants.keys()) |dep_index| {
-                const dep = mod.declPtr(dep_index);
-                switch (dep.analysis) {
-                    .unreferenced => unreachable,
-                    .in_progress => continue, // already doing analysis, ok
-                    .outdated => continue, // already queued for update
-
-                    .file_failure,
-                    .dependency_failure,
-                    .sema_failure,
-                    .sema_failure_retryable,
-                    .codegen_failure,
-                    .codegen_failure_retryable,
-                    .complete,
-                    => if (dep.generation != mod.generation) {
-                        try mod.markOutdatedDecl(dep_index);
-                    },
-                }
+        // Update all dependents which have at least this level of dependency.
+        // If our type remained the same and we're a function, only update
+        // decls which depend on our body; otherwise, update all dependents.
+        const update_level: Decl.DepType = if (!type_changed and decl.ty.zigTypeTag() == .Fn) .function_body else .normal;
+
+        for (decl.dependants.keys(), decl.dependants.values()) |dep_index, dep_type| {
+            if (@enumToInt(dep_type) < @enumToInt(update_level)) continue;
+
+            const dep = mod.declPtr(dep_index);
+            switch (dep.analysis) {
+                .unreferenced => unreachable,
+                .in_progress => continue, // already doing analysis, ok
+                .outdated => continue, // already queued for update
+
+                .file_failure,
+                .dependency_failure,
+                .sema_failure,
+                .sema_failure_retryable,
+                .codegen_failure,
+                .codegen_failure_retryable,
+                .complete,
+                => if (dep.generation != mod.generation) {
+                    try mod.markOutdatedDecl(dep_index);
+                },
             }
         }
     }
@@ -4742,25 +4768,37 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
 
 /// Returns the depender's index of the dependee.
 pub fn declareDeclDependency(mod: *Module, depender_index: Decl.Index, dependee_index: Decl.Index) !void {
+    return mod.declareDeclDependencyType(depender_index, dependee_index, .normal);
+}
+
+/// Returns the depender's index of the dependee.
+pub fn declareDeclDependencyType(mod: *Module, depender_index: Decl.Index, dependee_index: Decl.Index, dep_type: Decl.DepType) !void {
     if (depender_index == dependee_index) return;
 
     const depender = mod.declPtr(depender_index);
     const dependee = mod.declPtr(dependee_index);
 
+    if (depender.dependencies.get(dependee_index)) |cur_type| {
+        if (@enumToInt(cur_type) >= @enumToInt(dep_type)) {
+            // We already have this dependency (or stricter) marked
+            return;
+        }
+    }
+
     log.debug("{*} ({s}) depends on {*} ({s})", .{
         depender, depender.name, dependee, dependee.name,
     });
 
-    try depender.dependencies.ensureUnusedCapacity(mod.gpa, 1);
-    try dependee.dependants.ensureUnusedCapacity(mod.gpa, 1);
-
     if (dependee.deletion_flag) {
         dependee.deletion_flag = false;
         assert(mod.deletion_set.swapRemove(dependee_index));
     }
 
-    dependee.dependants.putAssumeCapacity(depender_index, {});
-    depender.dependencies.putAssumeCapacity(dependee_index, {});
+    try depender.dependencies.ensureUnusedCapacity(mod.gpa, 1);
+    try dependee.dependants.ensureUnusedCapacity(mod.gpa, 1);
+
+    dependee.dependants.putAssumeCapacity(depender_index, dep_type);
+    depender.dependencies.putAssumeCapacity(dependee_index, dep_type);
 }
 
 pub const ImportFileResult = struct {
@@ -6342,8 +6380,8 @@ pub fn populateTestFunctions(
                 try mod.declPtr(test_name_decl_index).finalizeNewArena(&name_decl_arena);
                 break :n test_name_decl_index;
             };
-            array_decl.dependencies.putAssumeCapacityNoClobber(test_decl_index, {});
-            array_decl.dependencies.putAssumeCapacityNoClobber(test_name_decl_index, {});
+            array_decl.dependencies.putAssumeCapacityNoClobber(test_decl_index, .normal);
+            array_decl.dependencies.putAssumeCapacityNoClobber(test_name_decl_index, .normal);
             try mod.linkerUpdateDecl(test_name_decl_index);
 
             const field_vals = try arena.create([3]Value);
src/Sema.zig
@@ -6635,6 +6635,8 @@ fn analyzeCall(
         sema.code = fn_owner_decl.getFileScope().zir;
         defer sema.code = parent_zir;
 
+        try mod.declareDeclDependencyType(sema.owner_decl_index, module_fn.owner_decl, .function_body);
+
         const parent_inst_map = sema.inst_map;
         sema.inst_map = .{};
         defer {
@@ -7331,7 +7333,7 @@ fn instantiateGenericCall(
         // The generic function Decl is guaranteed to be the first dependency
         // of each of its instantiations.
         assert(new_decl.dependencies.keys().len == 0);
-        try mod.declareDeclDependency(new_decl_index, module_fn.owner_decl);
+        try mod.declareDeclDependencyType(new_decl_index, module_fn.owner_decl, .function_body);
 
         var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
         const new_decl_arena_allocator = new_decl_arena.allocator();