aboutsummaryrefslogtreecommitdiff
path: root/src/Zcu/PerThread.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2024-08-16 12:46:52 +0100
committerJacob Young <jacobly0@users.noreply.github.com>2024-08-17 18:50:10 -0400
commit84c2ebd6c6b16752d8d030d5904d0a525283cbf5 (patch)
tree442234d4a737c6fbcf4f165ac18fa2a31cef9ac1 /src/Zcu/PerThread.zig
parent65cbdefe4d923efd8fd2cfb555cc02a52c5635fc (diff)
downloadzig-84c2ebd6c6b16752d8d030d5904d0a525283cbf5.tar.gz
zig-84c2ebd6c6b16752d8d030d5904d0a525283cbf5.zip
frontend: incremental compilation progress
Another big commit, sorry! This commit makes all fixes necessary for incremental updates of the compiler itself (specifically, adding a breakpoint to `zirCompileLog`) to succeed, at least on the frontend. The biggest change here is a reform to how types are handled. It works like this: * When a type is first created in `zirStructDecl` etc, its namespace is scanned. If the type requires resolution, an `interned` dependency is declared for the containing `AnalUnit`. * `zirThis` also declared an `interned` dependency for its `AnalUnit` on the namespace's owner type. * If the type's namespace changes, the surrounding source declaration changes hash, so `zirStructDecl` etc will be hit again. We check whether the namespace has been scanned this generation, and re-scan it if not. * Namespace lookups also check whether the namespace in question requires a re-scan based on the generation. This is because there's no guarantee that the `zirStructDecl` is re-analyzed before the namespace lookup is re-analyzed. * If a type's structure (essentially its fields) change, then the type's `Cau` is considered outdated. When the type is re-analyzed due to being outdated, or the `zirStructDecl` is re-analyzed by being transitively outdated, or a corresponding `zirThis` is re-analyzed by being transitively outdated, the struct type is recreated at a new `InternPool` index. The namespace's owner is updated (but not re-scanned, since that is handled by the mechanisms above), and the old type, while remaining a valid `Index`, is removed from the map metadata so it will never be found by lookups. `zirStructDecl` and `zirThis` store an `interned` dependency on the *new* type.
Diffstat (limited to 'src/Zcu/PerThread.zig')
-rw-r--r--src/Zcu/PerThread.zig654
1 files changed, 571 insertions, 83 deletions
diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig
index 83a7dce4fc..b2f6d600e6 100644
--- a/src/Zcu/PerThread.zig
+++ b/src/Zcu/PerThread.zig
@@ -485,10 +485,7 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
const file_root_type = pt.zcu.fileRootType(file_index);
if (file_root_type != .none) {
- // The namespace is already up-to-date thanks to the `updateFileNamespace` calls at the
- // start of this update. We just have to check whether the type itself is okay!
- const file_root_type_cau = pt.zcu.intern_pool.loadStructType(file_root_type).cau.unwrap().?;
- return pt.ensureCauAnalyzed(file_root_type_cau);
+ _ = try pt.ensureTypeUpToDate(file_root_type, false);
} else {
return pt.semaFile(file_index);
}
@@ -505,10 +502,10 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
- const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index });
+ const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
const cau = ip.getCau(cau_index);
- //log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)});
+ log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)});
assert(!zcu.analysis_in_progress.contains(anal_unit));
@@ -552,10 +549,12 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
// Since it does not, this must be a transitive failure.
try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
}
- // We treat errors as up-to-date, since those uses would just trigger a transitive error
+ // We treat errors as up-to-date, since those uses would just trigger a transitive error.
+ // The exception is types, since type declarations may require re-analysis if the type, e.g. its captures, changed.
+ const outdated = cau.owner.unwrap() == .type;
break :res .{ .{
- .invalidate_decl_val = false,
- .invalidate_decl_ref = false,
+ .invalidate_decl_val = outdated,
+ .invalidate_decl_ref = outdated,
}, true };
},
error.OutOfMemory => res: {
@@ -610,7 +609,7 @@ fn ensureCauAnalyzedInner(
const ip = &zcu.intern_pool;
const cau = ip.getCau(cau_index);
- const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index });
+ const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
@@ -626,7 +625,6 @@ fn ensureCauAnalyzedInner(
// * so, it uses the same `struct`
// * but this doesn't stop it from updating the namespace!
// * we basically do `scanDecls`, updating the namespace as needed
- // * TODO: optimize this to make sure we only do it once a generation i guess?
// * so everyone lived happily ever after
if (zcu.fileByIndex(inst_info.file).status != .success_zir) {
@@ -646,17 +644,6 @@ fn ensureCauAnalyzedInner(
_ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
}
- if (inst_info.inst == .main_struct_inst) {
- // Note that this is definitely a *recreation* due to outdated, because
- // this instruction indicates that `cau.owner` is a `type`, which only
- // reaches here if `cau_outdated`.
- try pt.recreateFileRoot(inst_info.file);
- return .{
- .invalidate_decl_val = true,
- .invalidate_decl_ref = true,
- };
- }
-
const decl_prog_node = zcu.sema_prog_node.start(switch (cau.owner.unwrap()) {
.nav => |nav| ip.getNav(nav).fqn.toSlice(ip),
.type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
@@ -685,9 +672,9 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
const func = zcu.funcInfo(maybe_coerced_func_index);
- //log.debug("ensureFuncBodyAnalyzed {d}", .{@intFromEnum(func_index)});
+ log.debug("ensureFuncBodyAnalyzed {d}", .{@intFromEnum(func_index)});
- const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index });
+ const anal_unit = AnalUnit.wrap(.{ .func = func_index });
const func_outdated = zcu.outdated.swapRemove(anal_unit) or
zcu.potentially_outdated.swapRemove(anal_unit);
@@ -742,7 +729,7 @@ fn ensureFuncBodyAnalyzedInner(
const ip = &zcu.intern_pool;
const func = zcu.funcInfo(func_index);
- const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index });
+ const anal_unit = AnalUnit.wrap(.{ .func = func_index });
// Here's an interesting question: is this function actually valid?
// Maybe the signature changed, so we'll end up creating a whole different `func`
@@ -766,7 +753,7 @@ fn ensureFuncBodyAnalyzedInner(
if (func_outdated) {
try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); // IES
}
- ip.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .func = func_index }));
+ ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .func = func_index }));
ip.remove(pt.tid, func_index);
@panic("TODO: remove orphaned function from binary");
}
@@ -901,7 +888,7 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
"unable to codegen: {s}",
.{@errorName(err)},
));
- try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .func = func_index }));
+ try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index }));
},
};
} else if (zcu.llvm_object) |llvm_object| {
@@ -982,7 +969,7 @@ fn createFileRootStruct(
if (zcu.comp.incremental) {
try ip.addDependency(
gpa,
- InternPool.AnalUnit.wrap(.{ .cau = new_cau_index }),
+ AnalUnit.wrap(.{ .cau = new_cau_index }),
.{ .src_hash = tracked_inst },
);
}
@@ -998,35 +985,6 @@ fn createFileRootStruct(
return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index);
}
-/// Recreate the root type of a file after it becomes outdated. A new struct type
-/// is constructed at a new InternPool index, reusing the namespace for efficiency.
-fn recreateFileRoot(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
- const zcu = pt.zcu;
- const ip = &zcu.intern_pool;
- const file = zcu.fileByIndex(file_index);
- const file_root_type = zcu.fileRootType(file_index);
- const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu);
-
- assert(file_root_type != .none);
-
- log.debug("recreateFileRoot mod={s} sub_file_path={s}", .{
- file.mod.fully_qualified_name,
- file.sub_file_path,
- });
-
- if (file.status != .success_zir) {
- return error.AnalysisFail;
- }
-
- // Invalidate the existing type, reusing its namespace.
- const file_root_type_cau = ip.loadStructType(file_root_type).cau.unwrap().?;
- ip.removeDependenciesForDepender(
- zcu.gpa,
- InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }),
- );
- _ = try pt.createFileRootStruct(file_index, namespace_index, true);
-}
-
/// Re-scan the namespace of a file's root struct type on an incremental update.
/// The file must have successfully populated ZIR.
/// If the file's root struct type is not populated (the file is unreferenced), nothing is done.
@@ -1060,6 +1018,7 @@ fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.
break :decls file.zir.bodySlice(extra_index, decls_len);
};
try pt.scanNamespace(namespace_index, decls);
+ zcu.namespacePtr(namespace_index).generation = zcu.generation;
}
fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
@@ -1080,6 +1039,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
.parent = .none,
.owner_type = undefined, // set in `createFileRootStruct`
.file_scope = file_index,
+ .generation = zcu.generation,
});
const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false);
errdefer zcu.intern_pool.remove(pt.tid, struct_ty);
@@ -1131,7 +1091,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
- const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index });
+ const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
const cau = ip.getCau(cau_index);
const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
@@ -1151,10 +1111,12 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
// This declaration has no value so is definitely not a std.builtin type.
break :ip_index .none;
},
- .type => {
+ .type => |ty| {
// This is an incremental update, and this type is being re-analyzed because it is outdated.
- // The type must be recreated at a new `InternPool.Index`.
- // Mark it outdated so that creation sites are re-analyzed.
+ // Create a new type in its place, and mark the old one as outdated so that use sites will
+ // be re-analyzed and discover an up-to-date type.
+ const new_ty = try pt.ensureTypeUpToDate(ty, true);
+ assert(new_ty != ty);
return .{
.invalidate_decl_val = true,
.invalidate_decl_ref = true,
@@ -2002,21 +1964,23 @@ const ScanDeclIter = struct {
try namespace.other_decls.append(gpa, cau);
- // For a `comptime` declaration, whether to re-analyze is based solely on whether the
- // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already.
- const unit = InternPool.AnalUnit.wrap(.{ .cau = cau });
- if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| {
- try zcu.outdated.ensureUnusedCapacity(gpa, 1);
- try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
- zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value);
- if (kv.value == 0) { // no PO deps
+ if (existing_cau == null) {
+ // For a `comptime` declaration, whether to analyze is based solely on whether the
+ // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already.
+ const unit = AnalUnit.wrap(.{ .cau = cau });
+ if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| {
+ try zcu.outdated.ensureUnusedCapacity(gpa, 1);
+ try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
+ zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value);
+ if (kv.value == 0) { // no PO deps
+ zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
+ }
+ } else if (!zcu.outdated.contains(unit)) {
+ try zcu.outdated.ensureUnusedCapacity(gpa, 1);
+ try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
+ zcu.outdated.putAssumeCapacityNoClobber(unit, 0);
zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
}
- } else if (!zcu.outdated.contains(unit)) {
- try zcu.outdated.ensureUnusedCapacity(gpa, 1);
- try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
- zcu.outdated.putAssumeCapacityNoClobber(unit, 0);
- zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
}
break :cau .{ cau, true };
@@ -2027,9 +1991,6 @@ const ScanDeclIter = struct {
const cau, const nav = if (existing_cau) |cau_index| cau_nav: {
const nav_index = ip.getCau(cau_index).owner.unwrap().nav;
const nav = ip.getNav(nav_index);
- if (nav.name != name) {
- std.debug.panic("'{}' vs '{}'", .{ nav.name.fmt(ip), name.fmt(ip) });
- }
assert(nav.name == name);
assert(nav.fqn == fqn);
break :cau_nav .{ cau_index, nav_index };
@@ -2078,7 +2039,7 @@ const ScanDeclIter = struct {
},
};
- if (want_analysis or declaration.flags.is_export) {
+ if (existing_cau == null and (want_analysis or declaration.flags.is_export)) {
log.debug(
"scanDecl queue analyze_cau file='{s}' cau_index={d}",
.{ namespace.fileScope(zcu).sub_file_path, cau },
@@ -2098,7 +2059,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
- const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index });
+ const anal_unit = AnalUnit.wrap(.{ .func = func_index });
const func = zcu.funcInfo(func_index);
const inst_info = func.zir_body_inst.resolveFull(ip) orelse return error.AnalysisFail;
const file = zcu.fileByIndex(inst_info.file);
@@ -2484,7 +2445,7 @@ fn processExportsInner(
const nav = ip.getNav(nav_index);
if (zcu.failed_codegen.contains(nav_index)) break :failed true;
if (nav.analysis_owner.unwrap()) |cau| {
- const cau_unit = InternPool.AnalUnit.wrap(.{ .cau = cau });
+ const cau_unit = AnalUnit.wrap(.{ .cau = cau });
if (zcu.failed_analysis.contains(cau_unit)) break :failed true;
if (zcu.transitive_failed_analysis.contains(cau_unit)) break :failed true;
}
@@ -2494,7 +2455,7 @@ fn processExportsInner(
};
// If the value is a function, we also need to check if that function succeeded analysis.
if (val.typeOf(zcu).zigTypeTag(zcu) == .Fn) {
- const func_unit = InternPool.AnalUnit.wrap(.{ .func = val.toIntern() });
+ const func_unit = AnalUnit.wrap(.{ .func = val.toIntern() });
if (zcu.failed_analysis.contains(func_unit)) break :failed true;
if (zcu.transitive_failed_analysis.contains(func_unit)) break :failed true;
}
@@ -2669,7 +2630,7 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void
.{@errorName(err)},
));
if (nav.analysis_owner.unwrap()) |cau| {
- try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .cau = cau }));
+ try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .cau = cau }));
} else {
// TODO: we don't have a way to indicate that this failure is retryable!
// Since these are really rare, we could as a cop-out retry the whole build next update.
@@ -2782,7 +2743,7 @@ pub fn reportRetryableFileError(
gop.value_ptr.* = err_msg;
}
-/// Shortcut for calling `intern_pool.get`.
+///Shortcut for calling `intern_pool.get`.
pub fn intern(pt: Zcu.PerThread, key: InternPool.Key) Allocator.Error!InternPool.Index {
return pt.zcu.intern_pool.get(pt.zcu.gpa, pt.tid, key);
}
@@ -3367,6 +3328,532 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo
return Value.fromInterned(r.val).typeOf(zcu).abiAlignment(pt);
}
+/// Given a container type requiring resolution, ensures that it is up-to-date.
+/// If not, the type is recreated at a new `InternPool.Index`.
+/// The new index is returned. This is the same as the old index if the fields were up-to-date.
+/// If `already_updating` is set, assumes the type is already outdated and undergoing re-analysis rather than checking `zcu.outdated`.
+pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index, already_updating: bool) Zcu.SemaError!InternPool.Index {
+ const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
+ switch (ip.indexToKey(ty)) {
+ .struct_type => |key| {
+ const struct_obj = ip.loadStructType(ty);
+ const outdated = already_updating or o: {
+ const anal_unit = AnalUnit.wrap(.{ .cau = struct_obj.cau.unwrap().? });
+ const o = zcu.outdated.swapRemove(anal_unit) or
+ zcu.potentially_outdated.swapRemove(anal_unit);
+ if (o) {
+ _ = zcu.outdated_ready.swapRemove(anal_unit);
+ try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
+ }
+ break :o o;
+ };
+ if (!outdated) return ty;
+ return pt.recreateStructType(ty, key, struct_obj);
+ },
+ .union_type => |key| {
+ const union_obj = ip.loadUnionType(ty);
+ const outdated = already_updating or o: {
+ const anal_unit = AnalUnit.wrap(.{ .cau = union_obj.cau });
+ const o = zcu.outdated.swapRemove(anal_unit) or
+ zcu.potentially_outdated.swapRemove(anal_unit);
+ if (o) {
+ _ = zcu.outdated_ready.swapRemove(anal_unit);
+ try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
+ }
+ break :o o;
+ };
+ if (!outdated) return ty;
+ return pt.recreateUnionType(ty, key, union_obj);
+ },
+ .enum_type => |key| {
+ const enum_obj = ip.loadEnumType(ty);
+ const outdated = already_updating or o: {
+ const anal_unit = AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? });
+ const o = zcu.outdated.swapRemove(anal_unit) or
+ zcu.potentially_outdated.swapRemove(anal_unit);
+ if (o) {
+ _ = zcu.outdated_ready.swapRemove(anal_unit);
+ try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
+ }
+ break :o o;
+ };
+ if (!outdated) return ty;
+ return pt.recreateEnumType(ty, key, enum_obj);
+ },
+ .opaque_type => {
+ assert(!already_updating);
+ return ty;
+ },
+ else => unreachable,
+ }
+}
+
+fn recreateStructType(
+ pt: Zcu.PerThread,
+ ty: InternPool.Index,
+ full_key: InternPool.Key.NamespaceType,
+ struct_obj: InternPool.LoadedStructType,
+) Zcu.SemaError!InternPool.Index {
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ip = &zcu.intern_pool;
+
+ const key = switch (full_key) {
+ .reified => unreachable, // never outdated
+ .empty_struct => unreachable, // never outdated
+ .generated_tag => unreachable, // not a struct
+ .declared => |d| d,
+ };
+
+ if (@intFromEnum(ty) <= InternPool.static_len) {
+ @panic("TODO: recreate resolved builtin type");
+ }
+
+ const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+ const file = zcu.fileByIndex(inst_info.file);
+ if (file.status != .success_zir) return error.AnalysisFail;
+ const zir = file.zir;
+
+ assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
+ const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended;
+ assert(extended.opcode == .struct_decl);
+ const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
+ const extra = zir.extraData(Zir.Inst.StructDecl, extended.operand);
+ var extra_index = extra.end;
+
+ const captures_len = if (small.has_captures_len) blk: {
+ const captures_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk captures_len;
+ } else 0;
+ const fields_len = if (small.has_fields_len) blk: {
+ const fields_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk fields_len;
+ } else 0;
+
+ if (captures_len != key.captures.owned.len) return error.AnalysisFail;
+ if (fields_len != struct_obj.field_types.len) return error.AnalysisFail;
+
+ // The old type will be unused, so drop its dependency information.
+ ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = struct_obj.cau.unwrap().? }));
+
+ const namespace_index = struct_obj.namespace.unwrap().?;
+
+ const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{
+ .layout = small.layout,
+ .fields_len = fields_len,
+ .known_non_opv = small.known_non_opv,
+ .requires_comptime = if (small.known_comptime_only) .yes else .unknown,
+ .is_tuple = small.is_tuple,
+ .any_comptime_fields = small.any_comptime_fields,
+ .any_default_inits = small.any_default_inits,
+ .inits_resolved = false,
+ .any_aligned_fields = small.any_aligned_fields,
+ .key = .{ .declared_owned_captures = .{
+ .zir_index = key.zir_index,
+ .captures = key.captures.owned,
+ } },
+ }, true)) {
+ .wip => |wip| wip,
+ .existing => unreachable, // we passed `replace_existing`
+ };
+ errdefer wip_ty.cancel(ip, pt.tid);
+
+ wip_ty.setName(ip, struct_obj.name);
+ const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index);
+ try ip.addDependency(
+ gpa,
+ AnalUnit.wrap(.{ .cau = new_cau_index }),
+ .{ .src_hash = key.zir_index },
+ );
+ zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
+ // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive.
+ try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
+
+ const new_ty = wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index);
+ if (inst_info.inst == .main_struct_inst) {
+ // This is the root type of a file! Update the reference.
+ zcu.setFileRootType(inst_info.file, new_ty);
+ }
+ return new_ty;
+}
+
+fn recreateUnionType(
+ pt: Zcu.PerThread,
+ ty: InternPool.Index,
+ full_key: InternPool.Key.NamespaceType,
+ union_obj: InternPool.LoadedUnionType,
+) Zcu.SemaError!InternPool.Index {
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ip = &zcu.intern_pool;
+
+ const key = switch (full_key) {
+ .reified => unreachable, // never outdated
+ .empty_struct => unreachable, // never outdated
+ .generated_tag => unreachable, // not a union
+ .declared => |d| d,
+ };
+
+ if (@intFromEnum(ty) <= InternPool.static_len) {
+ @panic("TODO: recreate resolved builtin type");
+ }
+
+ const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+ const file = zcu.fileByIndex(inst_info.file);
+ if (file.status != .success_zir) return error.AnalysisFail;
+ const zir = file.zir;
+
+ assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
+ const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended;
+ assert(extended.opcode == .union_decl);
+ const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small);
+ const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand);
+ var extra_index = extra.end;
+
+ extra_index += @intFromBool(small.has_tag_type);
+ const captures_len = if (small.has_captures_len) blk: {
+ const captures_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk captures_len;
+ } else 0;
+ extra_index += @intFromBool(small.has_body_len);
+ const fields_len = if (small.has_fields_len) blk: {
+ const fields_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk fields_len;
+ } else 0;
+
+ if (captures_len != key.captures.owned.len) return error.AnalysisFail;
+ if (fields_len != union_obj.field_types.len) return error.AnalysisFail;
+
+ // The old type will be unused, so drop its dependency information.
+ ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = union_obj.cau }));
+
+ const namespace_index = union_obj.namespace;
+
+ const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, .{
+ .flags = .{
+ .layout = small.layout,
+ .status = .none,
+ .runtime_tag = if (small.has_tag_type or small.auto_enum_tag)
+ .tagged
+ else if (small.layout != .auto)
+ .none
+ else switch (true) { // TODO
+ true => .safety,
+ false => .none,
+ },
+ .any_aligned_fields = small.any_aligned_fields,
+ .requires_comptime = .unknown,
+ .assumed_runtime_bits = false,
+ .assumed_pointer_aligned = false,
+ .alignment = .none,
+ },
+ .fields_len = fields_len,
+ .enum_tag_ty = .none, // set later
+ .field_types = &.{}, // set later
+ .field_aligns = &.{}, // set later
+ .key = .{ .declared_owned_captures = .{
+ .zir_index = key.zir_index,
+ .captures = key.captures.owned,
+ } },
+ }, true)) {
+ .wip => |wip| wip,
+ .existing => unreachable, // we passed `replace_existing`
+ };
+ errdefer wip_ty.cancel(ip, pt.tid);
+
+ wip_ty.setName(ip, union_obj.name);
+ const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index);
+ try ip.addDependency(
+ gpa,
+ AnalUnit.wrap(.{ .cau = new_cau_index }),
+ .{ .src_hash = key.zir_index },
+ );
+ zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
+ // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive.
+ try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
+ return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index);
+}
+
+fn recreateEnumType(
+ pt: Zcu.PerThread,
+ ty: InternPool.Index,
+ full_key: InternPool.Key.NamespaceType,
+ enum_obj: InternPool.LoadedEnumType,
+) Zcu.SemaError!InternPool.Index {
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ip = &zcu.intern_pool;
+
+ const key = switch (full_key) {
+ .reified => unreachable, // never outdated
+ .empty_struct => unreachable, // never outdated
+ .generated_tag => unreachable, // never outdated
+ .declared => |d| d,
+ };
+
+ if (@intFromEnum(ty) <= InternPool.static_len) {
+ @panic("TODO: recreate resolved builtin type");
+ }
+
+ const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+ const file = zcu.fileByIndex(inst_info.file);
+ if (file.status != .success_zir) return error.AnalysisFail;
+ const zir = file.zir;
+
+ assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
+ const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended;
+ assert(extended.opcode == .enum_decl);
+ const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small);
+ const extra = zir.extraData(Zir.Inst.EnumDecl, extended.operand);
+ var extra_index = extra.end;
+
+ const tag_type_ref = if (small.has_tag_type) blk: {
+ const tag_type_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
+ extra_index += 1;
+ break :blk tag_type_ref;
+ } else .none;
+
+ const captures_len = if (small.has_captures_len) blk: {
+ const captures_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk captures_len;
+ } else 0;
+
+ const body_len = if (small.has_body_len) blk: {
+ const body_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk body_len;
+ } else 0;
+
+ const fields_len = if (small.has_fields_len) blk: {
+ const fields_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk fields_len;
+ } else 0;
+
+ const decls_len = if (small.has_decls_len) blk: {
+ const decls_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk decls_len;
+ } else 0;
+
+ if (captures_len != key.captures.owned.len) return error.AnalysisFail;
+ if (fields_len != enum_obj.names.len) return error.AnalysisFail;
+
+ extra_index += captures_len;
+ extra_index += decls_len;
+
+ const body = zir.bodySlice(extra_index, body_len);
+ extra_index += body.len;
+
+ const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;
+ const body_end = extra_index;
+ extra_index += bit_bags_count;
+
+ const any_values = for (zir.extra[body_end..][0..bit_bags_count]) |bag| {
+ if (bag != 0) break true;
+ } else false;
+
+ // The old type will be unused, so drop its dependency information.
+ ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? }));
+
+ const namespace_index = enum_obj.namespace;
+
+ const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, .{
+ .has_values = any_values,
+ .tag_mode = if (small.nonexhaustive)
+ .nonexhaustive
+ else if (tag_type_ref == .none)
+ .auto
+ else
+ .explicit,
+ .fields_len = fields_len,
+ .key = .{ .declared_owned_captures = .{
+ .zir_index = key.zir_index,
+ .captures = key.captures.owned,
+ } },
+ }, true)) {
+ .wip => |wip| wip,
+ .existing => unreachable, // we passed `replace_existing`
+ };
+ var done = true;
+ errdefer if (!done) wip_ty.cancel(ip, pt.tid);
+
+ wip_ty.setName(ip, enum_obj.name);
+
+ const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index);
+
+ zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
+ // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive.
+
+ wip_ty.prepare(ip, new_cau_index, namespace_index);
+ done = true;
+
+ Sema.resolveDeclaredEnum(
+ pt,
+ wip_ty,
+ inst_info.inst,
+ key.zir_index,
+ namespace_index,
+ enum_obj.name,
+ new_cau_index,
+ small,
+ body,
+ tag_type_ref,
+ any_values,
+ fields_len,
+ zir,
+ body_end,
+ ) catch |err| switch (err) {
+ error.GenericPoison => unreachable,
+ error.ComptimeBreak => unreachable,
+ error.ComptimeReturn => unreachable,
+ error.AnalysisFail, error.OutOfMemory => |e| return e,
+ };
+
+ return wip_ty.index;
+}
+
+/// Given a namespace, re-scan its declarations from the type definition if they have not
+/// yet been re-scanned on this update.
+/// If the type declaration instruction has been lost, returns `error.AnalysisFail`.
+/// This will effectively short-circuit the caller, which will be semantic analysis of a
+/// guaranteed-unreferenced `AnalUnit`, to trigger a transitive analysis error.
+pub fn ensureNamespaceUpToDate(pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index) Zcu.SemaError!void {
+ const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
+ const namespace = zcu.namespacePtr(namespace_index);
+
+ if (namespace.generation == zcu.generation) return;
+
+ const Container = enum { @"struct", @"union", @"enum", @"opaque" };
+ const container: Container, const full_key = switch (ip.indexToKey(namespace.owner_type)) {
+ .struct_type => |k| .{ .@"struct", k },
+ .union_type => |k| .{ .@"union", k },
+ .enum_type => |k| .{ .@"enum", k },
+ .opaque_type => |k| .{ .@"opaque", k },
+ else => unreachable, // namespaces are owned by a container type
+ };
+
+ const key = switch (full_key) {
+ .reified, .empty_struct, .generated_tag => {
+ // Namespace always empty, so up-to-date.
+ namespace.generation = zcu.generation;
+ return;
+ },
+ .declared => |d| d,
+ };
+
+ // Namespace outdated -- re-scan the type if necessary.
+
+ const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+ const file = zcu.fileByIndex(inst_info.file);
+ if (file.status != .success_zir) return error.AnalysisFail;
+ const zir = file.zir;
+
+ assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
+ const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended;
+
+ const decls = switch (container) {
+ .@"struct" => decls: {
+ assert(extended.opcode == .struct_decl);
+ const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
+ const extra = zir.extraData(Zir.Inst.StructDecl, extended.operand);
+ var extra_index = extra.end;
+ const captures_len = if (small.has_captures_len) blk: {
+ const captures_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk captures_len;
+ } else 0;
+ extra_index += @intFromBool(small.has_fields_len);
+ const decls_len = if (small.has_decls_len) blk: {
+ const decls_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk decls_len;
+ } else 0;
+ extra_index += captures_len;
+ if (small.has_backing_int) {
+ const backing_int_body_len = zir.extra[extra_index];
+ extra_index += 1; // backing_int_body_len
+ if (backing_int_body_len == 0) {
+ extra_index += 1; // backing_int_ref
+ } else {
+ extra_index += backing_int_body_len; // backing_int_body_inst
+ }
+ }
+ break :decls zir.bodySlice(extra_index, decls_len);
+ },
+ .@"union" => decls: {
+ assert(extended.opcode == .union_decl);
+ const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small);
+ const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand);
+ var extra_index = extra.end;
+ extra_index += @intFromBool(small.has_tag_type);
+ const captures_len = if (small.has_captures_len) blk: {
+ const captures_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk captures_len;
+ } else 0;
+ extra_index += @intFromBool(small.has_body_len);
+ extra_index += @intFromBool(small.has_fields_len);
+ const decls_len = if (small.has_decls_len) blk: {
+ const decls_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk decls_len;
+ } else 0;
+ extra_index += captures_len;
+ break :decls zir.bodySlice(extra_index, decls_len);
+ },
+ .@"enum" => decls: {
+ assert(extended.opcode == .enum_decl);
+ const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small);
+ const extra = zir.extraData(Zir.Inst.EnumDecl, extended.operand);
+ var extra_index = extra.end;
+ extra_index += @intFromBool(small.has_tag_type);
+ const captures_len = if (small.has_captures_len) blk: {
+ const captures_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk captures_len;
+ } else 0;
+ extra_index += @intFromBool(small.has_body_len);
+ extra_index += @intFromBool(small.has_fields_len);
+ const decls_len = if (small.has_decls_len) blk: {
+ const decls_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk decls_len;
+ } else 0;
+ extra_index += captures_len;
+ break :decls zir.bodySlice(extra_index, decls_len);
+ },
+ .@"opaque" => decls: {
+ assert(extended.opcode == .opaque_decl);
+ const small: Zir.Inst.OpaqueDecl.Small = @bitCast(extended.small);
+ const extra = zir.extraData(Zir.Inst.OpaqueDecl, extended.operand);
+ var extra_index = extra.end;
+ const captures_len = if (small.has_captures_len) blk: {
+ const captures_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk captures_len;
+ } else 0;
+ const decls_len = if (small.has_decls_len) blk: {
+ const decls_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :blk decls_len;
+ } else 0;
+ extra_index += captures_len;
+ break :decls zir.bodySlice(extra_index, decls_len);
+ },
+ };
+
+ try pt.scanNamespace(namespace_index, decls);
+ namespace.generation = zcu.generation;
+}
+
const Air = @import("../Air.zig");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
@@ -3379,6 +3866,7 @@ const builtin = @import("builtin");
const Cache = std.Build.Cache;
const dev = @import("../dev.zig");
const InternPool = @import("../InternPool.zig");
+const AnalUnit = InternPool.AnalUnit;
const isUpDir = @import("../introspect.zig").isUpDir;
const Liveness = @import("../Liveness.zig");
const log = std.log.scoped(.zcu);