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.
This commit is contained in:
mlugg 2024-02-04 17:27:41 +00:00
parent 269c1ae649
commit 0d8207c292
No known key found for this signature in database
GPG Key ID: 58978E823BDE3EF9
4 changed files with 84 additions and 87 deletions

View File

@ -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| {

View File

@ -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,

View File

@ -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| {

View File

@ -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 => {},