Commit 0d8207c292

mlugg <mlugg@mlugg.co.uk>
2024-02-04 18:27:41
Zcu: refactor Decl.analysis field
* Functions failing codegen now set this failure on the function analysis state. Decl analysis `codegen_failure` is reserved for failures generating constant values. * `liveness_failure` is consolidated into `codegen_failure`, as we do not need to distinguish these, and Liveness.Verify is just a debugging feature anyway. * `sema_failure_retryable` and `codegen_failure_retryable` are removed. Instead, retryable failures are recorded in the new `Zcu.retryable_failures` list. On an incremental update, this list is flushed, and all elements are marked as outdated so that we re-attempt analysis and code generation. Also remove the `generation` fields from `Zcu` and `Decl` as these are not needed by our new strategy for incremental updates.
1 parent 269c1ae
src/Compilation.zig
@@ -2141,7 +2141,6 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
 
     if (comp.module) |module| {
         module.compile_log_text.shrinkAndFree(gpa, 0);
-        module.generation += 1;
 
         // Make sure std.zig is inside the import_table. We unconditionally need
         // it for start.zig.
@@ -3491,9 +3490,7 @@ pub fn performAllTheWork(
 
     if (comp.module) |mod| {
         try reportMultiModuleErrors(mod);
-    }
-
-    if (comp.module) |mod| {
+        try mod.flushRetryableFailures();
         mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
         mod.sema_prog_node.activate();
     }
@@ -3551,13 +3548,11 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v
 
                 .file_failure,
                 .sema_failure,
-                .liveness_failure,
                 .codegen_failure,
                 .dependency_failure,
-                .sema_failure_retryable,
                 => return,
 
-                .complete, .codegen_failure_retryable => {
+                .complete => {
                     const named_frame = tracy.namedFrame("codegen_decl");
                     defer named_frame.end();
 
@@ -3592,17 +3587,15 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v
             switch (decl.analysis) {
                 .unreferenced => unreachable,
                 .in_progress => unreachable,
-                .outdated => unreachable,
 
                 .file_failure,
                 .sema_failure,
                 .dependency_failure,
-                .sema_failure_retryable,
                 => return,
 
                 // emit-h only requires semantic analysis of the Decl to be complete,
                 // it does not depend on machine code generation to succeed.
-                .liveness_failure, .codegen_failure, .codegen_failure_retryable, .complete => {
+                .codegen_failure, .complete => {
                     const named_frame = tracy.namedFrame("emit_h_decl");
                     defer named_frame.end();
 
@@ -3674,7 +3667,8 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v
                     "unable to update line number: {s}",
                     .{@errorName(err)},
                 ));
-                decl.analysis = .codegen_failure_retryable;
+                decl.analysis = .codegen_failure;
+                try module.retryable_failures.append(gpa, InternPool.Depender.wrap(.{ .decl = decl_index }));
             };
         },
         .analyze_mod => |pkg| {
src/InternPool.zig
@@ -3483,6 +3483,11 @@ pub const FuncAnalysis = packed struct(u32) {
         /// This function might be OK but it depends on another Decl which did not
         /// successfully complete semantic analysis.
         dependency_failure,
+        /// There will be a corresponding ErrorMsg in Module.failed_decls.
+        /// Indicates that semantic analysis succeeded, but code generation for
+        /// this function failed.
+        codegen_failure,
+        /// Semantic analysis and code generation of this function succeeded.
         success,
     };
 };
@@ -6182,7 +6187,6 @@ pub const GetFuncInstanceKey = struct {
     is_noinline: bool,
     generic_owner: Index,
     inferred_error_set: bool,
-    generation: u32,
 };
 
 pub fn getFuncInstance(ip: *InternPool, gpa: Allocator, arg: GetFuncInstanceKey) Allocator.Error!Index {
@@ -6249,7 +6253,6 @@ pub fn getFuncInstance(ip: *InternPool, gpa: Allocator, arg: GetFuncInstanceKey)
         generic_owner,
         func_index,
         func_extra_index,
-        arg.generation,
         func_ty,
         arg.section,
     );
@@ -6381,7 +6384,6 @@ pub fn getFuncInstanceIes(
         generic_owner,
         func_index,
         func_extra_index,
-        arg.generation,
         func_ty,
         arg.section,
     );
@@ -6393,7 +6395,6 @@ fn finishFuncInstance(
     generic_owner: Index,
     func_index: Index,
     func_extra_index: u32,
-    generation: u32,
     func_ty: Index,
     section: OptionalNullTerminatedString,
 ) Allocator.Error!Index {
@@ -6413,7 +6414,6 @@ fn finishFuncInstance(
         .analysis = .complete,
         .zir_decl_index = fn_owner_decl.zir_decl_index,
         .src_scope = fn_owner_decl.src_scope,
-        .generation = generation,
         .is_pub = fn_owner_decl.is_pub,
         .is_exported = fn_owner_decl.is_exported,
         .alive = true,
src/Module.zig
@@ -144,11 +144,6 @@ global_error_set: GlobalErrorSet = .{},
 /// Maximum amount of distinct error values, set by --error-limit
 error_limit: ErrorInt,
 
-/// Incrementing integer used to compare against the corresponding Decl
-/// field to determine whether a Decl's status applies to an ongoing update, or a
-/// previous analysis.
-generation: u32 = 0,
-
 /// Value is the number of PO or outdated Decls which this Depender depends on.
 potentially_outdated: std.AutoArrayHashMapUnmanaged(InternPool.Depender, u32) = .{},
 /// Value is the number of PO or outdated Decls which this Depender depends on.
@@ -164,6 +159,11 @@ outdated_ready: std.AutoArrayHashMapUnmanaged(InternPool.Depender, void) = .{},
 /// (only the namespace might change). If such a Decl is also `outdated`, the
 /// struct type index must be recreated.
 outdated_file_root: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .{},
+/// This contains a list of Dependers whose analysis or codegen failed, but the
+/// failure was something like running out of disk space, and trying again may
+/// succeed. On the next update, we will flush this list, marking all members of
+/// it as outdated.
+retryable_failures: std.ArrayListUnmanaged(InternPool.Depender) = .{},
 
 stage1_flags: packed struct {
     have_winmain: bool = false,
@@ -380,21 +380,14 @@ pub const Decl = struct {
     alignment: Alignment,
     /// Populated when `has_tv`.
     @"addrspace": std.builtin.AddressSpace,
-    /// The direct parent namespace of the Decl.
-    /// Reference to externally owned memory.
-    /// In the case of the Decl corresponding to a file, this is
-    /// the namespace of the struct, since there is no parent.
+    /// The direct parent namespace of the Decl. In the case of the Decl
+    /// corresponding to a file, this is the namespace of the struct, since
+    /// there is no parent.
     src_namespace: Namespace.Index,
 
-    /// The scope which lexically contains this decl.  A decl must depend
-    /// on its lexical parent, in order to ensure that this pointer is valid.
-    /// This scope is allocated out of the arena of the parent decl.
+    /// The scope which lexically contains this decl.
     src_scope: CaptureScope.Index,
 
-    /// An integer that can be checked against the corresponding incrementing
-    /// generation field of Module. This is used to determine whether `complete` status
-    /// represents pre- or post- re-analysis.
-    generation: u32,
     /// The AST node index of this declaration.
     /// Must be recomputed when the corresponding source file is modified.
     src_node: Ast.Node.Index,
@@ -420,26 +413,19 @@ pub const Decl = struct {
         /// The file corresponding to this Decl had a parse error or ZIR error.
         /// There will be a corresponding ErrorMsg in Module.failed_files.
         file_failure,
-        /// This Decl might be OK but it depends on another one which did not successfully complete
-        /// semantic analysis.
+        /// This Decl might be OK but it depends on another one which did not
+        /// successfully complete semantic analysis.
         dependency_failure,
         /// Semantic analysis failure.
         /// There will be a corresponding ErrorMsg in Module.failed_decls.
         sema_failure,
         /// There will be a corresponding ErrorMsg in Module.failed_decls.
-        /// This indicates the failure was something like running out of disk space,
-        /// and attempting semantic analysis again may succeed.
-        sema_failure_retryable,
-        /// There will be a corresponding ErrorMsg in Module.failed_decls.
-        liveness_failure,
-        /// There will be a corresponding ErrorMsg in Module.failed_decls.
         codegen_failure,
-        /// There will be a corresponding ErrorMsg in Module.failed_decls.
-        /// This indicates the failure was something like running out of disk space,
-        /// and attempting codegen again may succeed.
-        codegen_failure_retryable,
-        /// Sematic analysis of this Decl has succeeded. However, the Decl may
-        /// be outdated due to an incomplete update!
+        /// Sematic analysis and constant value codegen of this Decl has
+        /// succeeded. However, the Decl may be outdated due to an in-progress
+        /// update. Note that for a function, this does not mean codegen of the
+        /// function body succeded: that state is indicated by the function's
+        /// `analysis` field.
         complete,
     },
     /// Whether `typed_value`, `align`, `linksection` and `addrspace` are populated.
@@ -2495,6 +2481,7 @@ pub fn deinit(zcu: *Zcu) void {
     zcu.outdated.deinit(gpa);
     zcu.outdated_ready.deinit(gpa);
     zcu.outdated_file_root.deinit(gpa);
+    zcu.retryable_failures.deinit(gpa);
 
     zcu.test_functions.deinit(gpa);
 
@@ -3257,6 +3244,29 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?InternPool.Depender {
     return InternPool.Depender.wrap(.{ .decl = chosen_decl_idx.? });
 }
 
+/// During an incremental update, before semantic analysis, call this to flush all values from
+/// `retryable_failures` and mark them as outdated so they get re-analyzed.
+pub fn flushRetryableFailures(zcu: *Zcu) !void {
+    const gpa = zcu.gpa;
+    for (zcu.retryable_failures.items) |depender| {
+        if (zcu.outdated.contains(depender)) continue;
+        if (zcu.potentially_outdated.fetchSwapRemove(depender)) |kv| {
+            // This Depender was already PO, but we now consider it outdated.
+            // Any transitive dependencies are already marked PO.
+            try zcu.outdated.put(gpa, depender, kv.value);
+            continue;
+        }
+        // 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 => {},
+        }
+    }
+    zcu.retryable_failures.clearRetainingCapacity();
+}
+
 pub fn mapOldZirToNew(
     gpa: Allocator,
     old_zir: Zir,
@@ -3415,15 +3425,11 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
     switch (decl.analysis) {
         .in_progress => unreachable,
 
-        .file_failure,
-        .liveness_failure,
-        .codegen_failure,
-        .codegen_failure_retryable,
-        .dependency_failure,
-        => return error.AnalysisFail,
+        .file_failure => return error.AnalysisFail,
 
         .sema_failure,
-        .sema_failure_retryable,
+        .dependency_failure,
+        .codegen_failure,
         => if (!was_outdated) return error.AnalysisFail,
 
         .complete => if (!was_outdated) return,
@@ -3434,6 +3440,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
     if (was_outdated) {
         // The exports this Decl performs will be re-discovered, so we remove them here
         // prior to re-analysis.
+        if (build_options.only_c) unreachable;
         try mod.deleteDeclExports(decl_index);
     }
 
@@ -3463,8 +3470,9 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
             error.NeededSourceLocation => unreachable,
             error.GenericPoison => unreachable,
             else => |e| {
-                decl.analysis = .sema_failure_retryable;
+                decl.analysis = .sema_failure;
                 try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
+                try mod.retryable_failures.append(mod.gpa, InternPool.Depender.wrap(.{ .decl = decl_index }));
                 mod.failed_decls.putAssumeCapacityNoClobber(decl_index, try ErrorMsg.create(
                     mod.gpa,
                     decl.srcLoc(mod),
@@ -3504,15 +3512,14 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
         .unreferenced => unreachable,
         .in_progress => unreachable,
 
+        .codegen_failure => unreachable, // functions do not perform constant value generation
+
         .file_failure,
         .sema_failure,
-        .liveness_failure,
-        .codegen_failure,
         .dependency_failure,
-        .sema_failure_retryable,
         => return error.AnalysisFail,
 
-        .complete, .codegen_failure_retryable => {},
+        .complete => {},
     }
 
     const func_as_depender = InternPool.Depender.wrap(.{ .func = func_index });
@@ -3524,11 +3531,14 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
     }
 
     switch (func.analysis(ip).state) {
-        .sema_failure, .dependency_failure => if (!was_outdated) return error.AnalysisFail,
+        .success,
+        .sema_failure,
+        .dependency_failure,
+        .codegen_failure,
+        => if (!was_outdated) return error.AnalysisFail,
         .none, .queued => {},
         .in_progress => unreachable,
         .inline_only => unreachable, // don't queue work for this
-        .success => if (!was_outdated) return,
     }
 
     const gpa = zcu.gpa;
@@ -3592,8 +3602,8 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
                         .{@errorName(err)},
                     ),
                 );
-                decl.analysis = .liveness_failure;
-                return error.AnalysisFail;
+                func.analysis(ip).state = .codegen_failure;
+                return;
             },
         };
     }
@@ -3602,7 +3612,7 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
         lf.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) {
             error.OutOfMemory => return error.OutOfMemory,
             error.AnalysisFail => {
-                decl.analysis = .codegen_failure;
+                func.analysis(ip).state = .codegen_failure;
             },
             else => {
                 try zcu.failed_decls.ensureUnusedCapacity(gpa, 1);
@@ -3612,7 +3622,8 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
-                decl.analysis = .codegen_failure_retryable;
+                func.analysis(ip).state = .codegen_failure;
+                try zcu.retryable_failures.append(zcu.gpa, InternPool.Depender.wrap(.{ .func = func_index }));
             },
         };
     } else if (zcu.llvm_object) |llvm_object| {
@@ -3620,7 +3631,7 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
         llvm_object.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) {
             error.OutOfMemory => return error.OutOfMemory,
             error.AnalysisFail => {
-                decl.analysis = .codegen_failure;
+                func.analysis(ip).state = .codegen_failure;
             },
         };
     }
@@ -3645,14 +3656,11 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index)
 
         .file_failure,
         .sema_failure,
-        .liveness_failure,
         .codegen_failure,
         .dependency_failure,
-        .sema_failure_retryable,
-        .codegen_failure_retryable,
-        // The function analysis failed, but we've already emitted an error for
-        // that. The callee doesn't need the function to be analyzed right now,
-        // so its analysis can safely continue.
+        // Analysis of the function Decl itself failed, but we've already
+        // emitted an error for that. The callee doesn't need the function to be
+        // analyzed right now, so its analysis can safely continue.
         => return,
 
         .complete => {},
@@ -3660,14 +3668,21 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index)
 
     assert(decl.has_tv);
 
+    const func_as_depender = InternPool.Depender.wrap(.{ .func = func_index });
+    const is_outdated = mod.outdated.contains(func_as_depender) or
+        mod.potentially_outdated.contains(func_as_depender);
+
     switch (func.analysis(ip).state) {
         .none => {},
         .queued => return,
         // As above, we don't need to forward errors here.
-        .sema_failure, .dependency_failure => return,
+        .sema_failure,
+        .dependency_failure,
+        .codegen_failure,
+        .success,
+        => if (!is_outdated) return,
         .in_progress => return,
         .inline_only => unreachable, // don't queue work for this
-        .success => return,
     }
 
     // Decl itself is safely analyzed, and body analysis is not yet queued
@@ -3727,7 +3742,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
     new_decl.@"linksection" = .none;
     new_decl.alive = true; // This Decl corresponds to a File and is therefore always alive.
     new_decl.analysis = .in_progress;
-    new_decl.generation = mod.generation;
 
     if (file.status != .success_zir) {
         new_decl.analysis = .file_failure;
@@ -3966,7 +3980,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
         decl.has_tv = true;
         decl.owns_tv = false;
         decl.analysis = .complete;
-        decl.generation = mod.generation;
 
         // TODO: usingnamespace cannot currently participate in incremental compilation
         return .{
@@ -3997,7 +4010,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
                 decl.has_tv = true;
                 decl.owns_tv = owns_tv;
                 decl.analysis = .complete;
-                decl.generation = mod.generation;
 
                 const is_inline = decl.ty.fnCallingConvention(mod) == .Inline;
                 if (decl.is_exported) {
@@ -4094,7 +4106,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
     };
     decl.has_tv = true;
     decl.analysis = .complete;
-    decl.generation = mod.generation;
 
     const result: SemaDeclResult = if (old_has_tv) .{
         .invalidate_decl_val = !decl.ty.eql(old_ty, mod) or !decl.val.eql(old_val, decl.ty, mod),
@@ -5005,7 +5016,6 @@ pub fn allocateNewDecl(
         .analysis = .unreferenced,
         .zir_decl_index = .none,
         .src_scope = src_scope,
-        .generation = 0,
         .is_pub = false,
         .is_exported = false,
         .alive = false,
@@ -5083,7 +5093,6 @@ pub fn initNewAnonDecl(
     new_decl.@"linksection" = .none;
     new_decl.has_tv = true;
     new_decl.analysis = .complete;
-    new_decl.generation = mod.generation;
 }
 
 pub fn errNoteNonLazy(
@@ -5745,7 +5754,8 @@ pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void {
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
-                decl.analysis = .codegen_failure_retryable;
+                decl.analysis = .codegen_failure;
+                try zcu.retryable_failures.append(zcu.gpa, InternPool.Depender.wrap(.{ .decl = decl_index }));
             },
         };
     } else if (zcu.llvm_object) |llvm_object| {
src/Sema.zig
@@ -2583,7 +2583,6 @@ fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Module.ErrorMsg)
         ip.funcAnalysis(sema.owner_func_index).state = .sema_failure;
     } else {
         sema.owner_decl.analysis = .sema_failure;
-        sema.owner_decl.generation = mod.generation;
     }
     if (sema.func_index != .none) {
         ip.funcAnalysis(sema.func_index).state = .sema_failure;
@@ -9468,7 +9467,6 @@ fn funcCommon(
             .inferred_error_set = inferred_error_set,
             .generic_owner = sema.generic_owner,
             .comptime_args = sema.comptime_args,
-            .generation = mod.generation,
         });
         return finishFunc(
             sema,
@@ -25957,7 +25955,6 @@ fn zirBuiltinExtern(
     new_decl.has_tv = true;
     new_decl.owns_tv = true;
     new_decl.analysis = .complete;
-    new_decl.generation = mod.generation;
 
     try sema.ensureDeclAnalyzed(new_decl_index);
 
@@ -36215,10 +36212,8 @@ pub fn resolveTypeFieldsStruct(
         .file_failure,
         .dependency_failure,
         .sema_failure,
-        .sema_failure_retryable,
         => {
             sema.owner_decl.analysis = .dependency_failure;
-            sema.owner_decl.generation = mod.generation;
             return error.AnalysisFail;
         },
         else => {},
@@ -36274,10 +36269,8 @@ pub fn resolveTypeFieldsUnion(sema: *Sema, ty: Type, union_type: InternPool.Key.
         .file_failure,
         .dependency_failure,
         .sema_failure,
-        .sema_failure_retryable,
         => {
             sema.owner_decl.analysis = .dependency_failure;
-            sema.owner_decl.generation = mod.generation;
             return error.AnalysisFail;
         },
         else => {},