authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-03 23:48:27+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-04 05:44:29+00:00
logf818098971fdafdaaa6af4b927f2cf47332ed5f7
tree68692d31a8c939887864ec302d8e8378c7c87cfd
parent814491f59921600b8a0137733b48d4b0f6b99d78
signaturelock-open Commit is signed but in an unrecognized format.

incremental: correctly return `error.AnalysisFail` when type structure changes

`Zcu.PerThead.ensureTypeUpToDate` is set up in such a way that it only returns the updated type the first time it is called. In general, that's okay; however, the exception is that we want the function to continue returning `error.AnalysisFail` when the type has been lost, or its number of captures changed. Therefore, the check for this case now happens before the up-to-date success return. For simplicity, the number of captures is now handled by intentionally losing the instruction in `Zcu.mapOldZirToNew`, since there is nothing to gain from tracking a type when old instances of it can never be reused.

5 files changed, 96 insertions(+), 62 deletions(-)

lib/std/zig/Zir.zig+32
......@@ -5066,3 +5066,35 @@ pub fn assertTrackable(zir: Zir, inst_idx: Zir.Inst.Index) void {
50665066 else => unreachable, // assertion failure; not trackable
50675067 }
50685068}
5069
5070pub fn typeCapturesLen(zir: Zir, type_decl: Inst.Index) u32 {
5071 const inst = zir.instructions.get(@intFromEnum(type_decl));
5072 assert(inst.tag == .extended);
5073 switch (inst.data.extended.opcode) {
5074 .struct_decl => {
5075 const small: Inst.StructDecl.Small = @bitCast(inst.data.extended.small);
5076 if (!small.has_captures_len) return 0;
5077 const extra = zir.extraData(Inst.StructDecl, inst.data.extended.operand);
5078 return zir.extra[extra.end];
5079 },
5080 .union_decl => {
5081 const small: Inst.UnionDecl.Small = @bitCast(inst.data.extended.small);
5082 if (!small.has_captures_len) return 0;
5083 const extra = zir.extraData(Inst.UnionDecl, inst.data.extended.operand);
5084 return zir.extra[extra.end + @intFromBool(small.has_tag_type)];
5085 },
5086 .enum_decl => {
5087 const small: Inst.EnumDecl.Small = @bitCast(inst.data.extended.small);
5088 if (!small.has_captures_len) return 0;
5089 const extra = zir.extraData(Inst.EnumDecl, inst.data.extended.operand);
5090 return zir.extra[extra.end + @intFromBool(small.has_tag_type)];
5091 },
5092 .opaque_decl => {
5093 const small: Inst.OpaqueDecl.Small = @bitCast(inst.data.extended.small);
5094 if (!small.has_captures_len) return 0;
5095 const extra = zir.extraData(Inst.OpaqueDecl, inst.data.extended.operand);
5096 return zir.extra[extra.end];
5097 },
5098 else => unreachable,
5099 }
5100}
src/InternPool.zig+11-9
......@@ -2029,15 +2029,7 @@ pub const Key = union(enum) {
20292029 pub const NamespaceType = union(enum) {
20302030 /// This type corresponds to an actual source declaration, e.g. `struct { ... }`.
20312031 /// It is hashed based on its ZIR instruction index and set of captures.
2032 declared: struct {
2033 /// A `struct_decl`, `union_decl`, `enum_decl`, or `opaque_decl` instruction.
2034 zir_index: TrackedInst.Index,
2035 /// The captured values of this type. These values must be fully resolved per the language spec.
2036 captures: union(enum) {
2037 owned: CaptureValue.Slice,
2038 external: []const CaptureValue,
2039 },
2040 },
2032 declared: Declared,
20412033 /// This type is an automatically-generated enum tag type for a union.
20422034 /// It is hashed based on the index of the union type it corresponds to.
20432035 generated_tag: struct {
......@@ -2053,6 +2045,16 @@ pub const Key = union(enum) {
20532045 /// A hash of this type's attributes, fields, etc, generated by Sema.
20542046 type_hash: u64,
20552047 },
2048
2049 pub const Declared = struct {
2050 /// A `struct_decl`, `union_decl`, `enum_decl`, or `opaque_decl` instruction.
2051 zir_index: TrackedInst.Index,
2052 /// The captured values of this type. These values must be fully resolved per the language spec.
2053 captures: union(enum) {
2054 owned: CaptureValue.Slice,
2055 external: []const CaptureValue,
2056 },
2057 };
20562058 };
20572059
20582060 pub const FuncType = struct {
src/Sema.zig+2
......@@ -18172,7 +18172,9 @@ fn zirThis(
1817218172 _ = extended;
1817318173 const pt = sema.pt;
1817418174 const namespace = pt.zcu.namespacePtr(block.namespace);
18175
1817518176 const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);
18177
1817618178 switch (pt.zcu.intern_pool.indexToKey(new_ty)) {
1817718179 .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }),
1817818180 .opaque_type => {},
src/Zcu.zig+8-13
......@@ -2563,19 +2563,6 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
25632563 }
25642564 }
25652565
2566 if (chosen_unit == null) {
2567 for (zcu.outdated.keys(), zcu.outdated.values()) |o, opod| {
2568 const func = o.unwrap().func;
2569 const nav = zcu.funcInfo(func).owner_nav;
2570 std.io.getStdErr().writer().print("outdated: func {}, nav {}, name '{}', [p]o deps {}\n", .{ func, nav, ip.getNav(nav).fqn.fmt(ip), opod }) catch {};
2571 }
2572 for (zcu.potentially_outdated.keys(), zcu.potentially_outdated.values()) |o, opod| {
2573 const func = o.unwrap().func;
2574 const nav = zcu.funcInfo(func).owner_nav;
2575 std.io.getStdErr().writer().print("po: func {}, nav {}, name '{}', [p]o deps {}\n", .{ func, nav, ip.getNav(nav).fqn.fmt(ip), opod }) catch {};
2576 }
2577 }
2578
25792566 log.debug("findOutdatedToAnalyze: heuristic returned '{}' ({d} dependers)", .{
25802567 zcu.fmtAnalUnit(chosen_unit.?),
25812568 chosen_unit_dependers,
......@@ -2661,6 +2648,14 @@ pub fn mapOldZirToNew(
26612648 }
26622649
26632650 while (match_stack.popOrNull()) |match_item| {
2651 // First, a check: if the number of captures of this type has changed, we can't map it, because
2652 // we wouldn't know how to correlate type information with the last update.
2653 // Synchronizes with logic in `Zcu.PerThread.recreateStructType` etc.
2654 if (old_zir.typeCapturesLen(match_item.old_inst) != new_zir.typeCapturesLen(match_item.new_inst)) {
2655 // Don't map this type or anything within it.
2656 continue;
2657 }
2658
26642659 // Match the namespace declaration itself
26652660 try inst_map.put(gpa, match_item.old_inst, match_item.new_inst);
26662661
src/Zcu/PerThread.zig+43-40
......@@ -3524,9 +3524,11 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo
35243524 return ty.abiAlignment(zcu);
35253525}
35263526
3527/// Given a container type requiring resolution, ensures that it is up-to-date.
3528/// If not, the type is recreated at a new `InternPool.Index`.
3529/// The new index is returned. This is the same as the old index if the fields were up-to-date.
3527/// `ty` is a container type requiring resolution (struct, union, or enum).
3528/// If `ty` is outdated, it is recreated at a new `InternPool.Index`, which is returned.
3529/// If the type cannot be recreated because it has been lost, `error.AnalysisFail` is returned.
3530/// If `ty` is not outdated, that same `InternPool.Index` is returned.
3531/// If `ty` has already been replaced by this function, the new index will not be returned again.
35303532pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index {
35313533 const zcu = pt.zcu;
35323534 const gpa = zcu.gpa;
......@@ -3536,13 +3538,30 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
35363538 const outdated = zcu.outdated.swapRemove(anal_unit) or
35373539 zcu.potentially_outdated.swapRemove(anal_unit);
35383540
3541 if (outdated) {
3542 _ = zcu.outdated_ready.swapRemove(anal_unit);
3543 try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
3544 }
3545
3546 const ty_key = switch (ip.indexToKey(ty)) {
3547 .struct_type, .union_type, .enum_type => |key| key,
3548 else => unreachable,
3549 };
3550 const declared_ty_key = switch (ty_key) {
3551 .reified => unreachable, // never outdated
3552 .generated_tag => unreachable, // never outdated
3553 .declared => |d| d,
3554 };
3555
3556 if (declared_ty_key.zir_index.resolve(ip) == null) {
3557 // The instruction has been lost -- this type is dead.
3558 return error.AnalysisFail;
3559 }
3560
35393561 if (!outdated) return ty;
35403562
35413563 // We will recreate the type at a new `InternPool.Index`.
35423564
3543 _ = zcu.outdated_ready.swapRemove(anal_unit);
3544 try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
3545
35463565 // Delete old state which is no longer in use. Technically, this is not necessary: these exports,
35473566 // references, etc, will be ignored because the type itself is unreferenced. However, it allows
35483567 // reusing the memory which is currently being used to track this state.
......@@ -3555,9 +3574,9 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
35553574 zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
35563575
35573576 switch (ip.indexToKey(ty)) {
3558 .struct_type => |key| return pt.recreateStructType(ty, key),
3559 .union_type => |key| return pt.recreateUnionType(ty, key),
3560 .enum_type => |key| return pt.recreateEnumType(ty, key),
3577 .struct_type => return pt.recreateStructType(ty, declared_ty_key),
3578 .union_type => return pt.recreateUnionType(ty, declared_ty_key),
3579 .enum_type => return pt.recreateEnumType(ty, declared_ty_key),
35613580 else => unreachable,
35623581 }
35633582}
......@@ -3565,21 +3584,15 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
35653584fn recreateStructType(
35663585 pt: Zcu.PerThread,
35673586 old_ty: InternPool.Index,
3568 full_key: InternPool.Key.NamespaceType,
3569) Zcu.SemaError!InternPool.Index {
3587 key: InternPool.Key.NamespaceType.Declared,
3588) Allocator.Error!InternPool.Index {
35703589 const zcu = pt.zcu;
35713590 const gpa = zcu.gpa;
35723591 const ip = &zcu.intern_pool;
35733592
3574 const key = switch (full_key) {
3575 .reified => unreachable, // never outdated
3576 .generated_tag => unreachable, // not a struct
3577 .declared => |d| d,
3578 };
3579
3580 const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
3593 const inst_info = key.zir_index.resolveFull(ip).?;
35813594 const file = zcu.fileByIndex(inst_info.file);
3582 if (file.status != .success_zir) return error.AnalysisFail;
3595 assert(file.status == .success_zir); // otherwise inst tracking failed
35833596 const zir = file.zir;
35843597
35853598 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
......@@ -3600,7 +3613,7 @@ fn recreateStructType(
36003613 break :blk fields_len;
36013614 } else 0;
36023615
3603 if (captures_len != key.captures.owned.len) return error.AnalysisFail;
3616 assert(captures_len == key.captures.owned.len); // synchronises with logic in `Zcu.mapOldZirToNew`
36043617
36053618 const struct_obj = ip.loadStructType(old_ty);
36063619
......@@ -3644,21 +3657,15 @@ fn recreateStructType(
36443657fn recreateUnionType(
36453658 pt: Zcu.PerThread,
36463659 old_ty: InternPool.Index,
3647 full_key: InternPool.Key.NamespaceType,
3648) Zcu.SemaError!InternPool.Index {
3660 key: InternPool.Key.NamespaceType.Declared,
3661) Allocator.Error!InternPool.Index {
36493662 const zcu = pt.zcu;
36503663 const gpa = zcu.gpa;
36513664 const ip = &zcu.intern_pool;
36523665
3653 const key = switch (full_key) {
3654 .reified => unreachable, // never outdated
3655 .generated_tag => unreachable, // not a union
3656 .declared => |d| d,
3657 };
3658
3659 const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
3666 const inst_info = key.zir_index.resolveFull(ip).?;
36603667 const file = zcu.fileByIndex(inst_info.file);
3661 if (file.status != .success_zir) return error.AnalysisFail;
3668 assert(file.status == .success_zir); // otherwise inst tracking failed
36623669 const zir = file.zir;
36633670
36643671 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
......@@ -3681,7 +3688,7 @@ fn recreateUnionType(
36813688 break :blk fields_len;
36823689 } else 0;
36833690
3684 if (captures_len != key.captures.owned.len) return error.AnalysisFail;
3691 assert(captures_len == key.captures.owned.len); // synchronises with logic in `Zcu.mapOldZirToNew`
36853692
36863693 const union_obj = ip.loadUnionType(old_ty);
36873694
......@@ -3731,23 +3738,19 @@ fn recreateUnionType(
37313738 return wip_ty.finish(ip, namespace_index);
37323739}
37333740
3741// TODO: is it safe for this to return `SemaError`? enum type resolution is a bit weird...
37343742fn recreateEnumType(
37353743 pt: Zcu.PerThread,
37363744 old_ty: InternPool.Index,
3737 full_key: InternPool.Key.NamespaceType,
3745 key: InternPool.Key.NamespaceType.Declared,
37383746) Zcu.SemaError!InternPool.Index {
37393747 const zcu = pt.zcu;
37403748 const gpa = zcu.gpa;
37413749 const ip = &zcu.intern_pool;
37423750
3743 const key = switch (full_key) {
3744 .reified, .generated_tag => unreachable, // never outdated
3745 .declared => |d| d,
3746 };
3747
3748 const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
3751 const inst_info = key.zir_index.resolveFull(ip).?;
37493752 const file = zcu.fileByIndex(inst_info.file);
3750 if (file.status != .success_zir) return error.AnalysisFail;
3753 assert(file.status == .success_zir); // otherwise inst tracking failed
37513754 const zir = file.zir;
37523755
37533756 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
......@@ -3787,7 +3790,7 @@ fn recreateEnumType(
37873790 break :blk decls_len;
37883791 } else 0;
37893792
3790 if (captures_len != key.captures.owned.len) return error.AnalysisFail;
3793 assert(captures_len == key.captures.owned.len); // synchronises with logic in `Zcu.mapOldZirToNew`
37913794
37923795 extra_index += captures_len;
37933796 extra_index += decls_len;