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 {...@@ -5066,3 +5066,35 @@ pub fn assertTrackable(zir: Zir, inst_idx: Zir.Inst.Index) void {
5066 else => unreachable, // assertion failure; not trackable5066 else => unreachable, // assertion failure; not trackable
5067 }5067 }
5068}5068}
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) {...@@ -2029,15 +2029,7 @@ pub const Key = union(enum) {
2029 pub const NamespaceType = union(enum) {2029 pub const NamespaceType = union(enum) {
2030 /// This type corresponds to an actual source declaration, e.g. `struct { ... }`.2030 /// This type corresponds to an actual source declaration, e.g. `struct { ... }`.
2031 /// It is hashed based on its ZIR instruction index and set of captures.2031 /// It is hashed based on its ZIR instruction index and set of captures.
2032 declared: struct {2032 declared: Declared,
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 },
2041 /// This type is an automatically-generated enum tag type for a union.2033 /// This type is an automatically-generated enum tag type for a union.
2042 /// It is hashed based on the index of the union type it corresponds to.2034 /// It is hashed based on the index of the union type it corresponds to.
2043 generated_tag: struct {2035 generated_tag: struct {
...@@ -2053,6 +2045,16 @@ pub const Key = union(enum) {...@@ -2053,6 +2045,16 @@ pub const Key = union(enum) {
2053 /// A hash of this type's attributes, fields, etc, generated by Sema.2045 /// A hash of this type's attributes, fields, etc, generated by Sema.
2054 type_hash: u64,2046 type_hash: u64,
2055 },2047 },
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 };
2056 };2058 };
20572059
2058 pub const FuncType = struct {2060 pub const FuncType = struct {
src/Sema.zig+2
...@@ -18172,7 +18172,9 @@ fn zirThis(...@@ -18172,7 +18172,9 @@ fn zirThis(
18172 _ = extended;18172 _ = extended;
18173 const pt = sema.pt;18173 const pt = sema.pt;
18174 const namespace = pt.zcu.namespacePtr(block.namespace);18174 const namespace = pt.zcu.namespacePtr(block.namespace);
18175
18175 const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);18176 const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);
18177
18176 switch (pt.zcu.intern_pool.indexToKey(new_ty)) {18178 switch (pt.zcu.intern_pool.indexToKey(new_ty)) {
18177 .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }),18179 .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }),
18178 .opaque_type => {},18180 .opaque_type => {},
src/Zcu.zig+8-13
...@@ -2563,19 +2563,6 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {...@@ -2563,19 +2563,6 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
2563 }2563 }
2564 }2564 }
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
2579 log.debug("findOutdatedToAnalyze: heuristic returned '{}' ({d} dependers)", .{2566 log.debug("findOutdatedToAnalyze: heuristic returned '{}' ({d} dependers)", .{
2580 zcu.fmtAnalUnit(chosen_unit.?),2567 zcu.fmtAnalUnit(chosen_unit.?),
2581 chosen_unit_dependers,2568 chosen_unit_dependers,
...@@ -2661,6 +2648,14 @@ pub fn mapOldZirToNew(...@@ -2661,6 +2648,14 @@ pub fn mapOldZirToNew(
2661 }2648 }
26622649
2663 while (match_stack.popOrNull()) |match_item| {2650 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
2664 // Match the namespace declaration itself2659 // Match the namespace declaration itself
2665 try inst_map.put(gpa, match_item.old_inst, match_item.new_inst);2660 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...@@ -3524,9 +3524,11 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo
3524 return ty.abiAlignment(zcu);3524 return ty.abiAlignment(zcu);
3525}3525}
35263526
3527/// Given a container type requiring resolution, ensures that it is up-to-date.3527/// `ty` is a container type requiring resolution (struct, union, or enum).
3528/// If not, the type is recreated at a new `InternPool.Index`.3528/// If `ty` is outdated, it is recreated at a new `InternPool.Index`, which is returned.
3529/// The new index is returned. This is the same as the old index if the fields were up-to-date.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.
3530pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index {3532pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index {
3531 const zcu = pt.zcu;3533 const zcu = pt.zcu;
3532 const gpa = zcu.gpa;3534 const gpa = zcu.gpa;
...@@ -3536,13 +3538,30 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError...@@ -3536,13 +3538,30 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
3536 const outdated = zcu.outdated.swapRemove(anal_unit) or3538 const outdated = zcu.outdated.swapRemove(anal_unit) or
3537 zcu.potentially_outdated.swapRemove(anal_unit);3539 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
3539 if (!outdated) return ty;3561 if (!outdated) return ty;
35403562
3541 // We will recreate the type at a new `InternPool.Index`.3563 // 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
3546 // Delete old state which is no longer in use. Technically, this is not necessary: these exports,3565 // Delete old state which is no longer in use. Technically, this is not necessary: these exports,
3547 // references, etc, will be ignored because the type itself is unreferenced. However, it allows3566 // references, etc, will be ignored because the type itself is unreferenced. However, it allows
3548 // reusing the memory which is currently being used to track this state.3567 // 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...@@ -3555,9 +3574,9 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
3555 zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);3574 zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
35563575
3557 switch (ip.indexToKey(ty)) {3576 switch (ip.indexToKey(ty)) {
3558 .struct_type => |key| return pt.recreateStructType(ty, key),3577 .struct_type => return pt.recreateStructType(ty, declared_ty_key),
3559 .union_type => |key| return pt.recreateUnionType(ty, key),3578 .union_type => return pt.recreateUnionType(ty, declared_ty_key),
3560 .enum_type => |key| return pt.recreateEnumType(ty, key),3579 .enum_type => return pt.recreateEnumType(ty, declared_ty_key),
3561 else => unreachable,3580 else => unreachable,
3562 }3581 }
3563}3582}
...@@ -3565,21 +3584,15 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError...@@ -3565,21 +3584,15 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
3565fn recreateStructType(3584fn recreateStructType(
3566 pt: Zcu.PerThread,3585 pt: Zcu.PerThread,
3567 old_ty: InternPool.Index,3586 old_ty: InternPool.Index,
3568 full_key: InternPool.Key.NamespaceType,3587 key: InternPool.Key.NamespaceType.Declared,
3569) Zcu.SemaError!InternPool.Index {3588) Allocator.Error!InternPool.Index {
3570 const zcu = pt.zcu;3589 const zcu = pt.zcu;
3571 const gpa = zcu.gpa;3590 const gpa = zcu.gpa;
3572 const ip = &zcu.intern_pool;3591 const ip = &zcu.intern_pool;
35733592
3574 const key = switch (full_key) {3593 const inst_info = key.zir_index.resolveFull(ip).?;
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;
3581 const file = zcu.fileByIndex(inst_info.file);3594 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
3583 const zir = file.zir;3596 const zir = file.zir;
35843597
3585 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);3598 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
...@@ -3600,7 +3613,7 @@ fn recreateStructType(...@@ -3600,7 +3613,7 @@ fn recreateStructType(
3600 break :blk fields_len;3613 break :blk fields_len;
3601 } else 0;3614 } 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
3605 const struct_obj = ip.loadStructType(old_ty);3618 const struct_obj = ip.loadStructType(old_ty);
36063619
...@@ -3644,21 +3657,15 @@ fn recreateStructType(...@@ -3644,21 +3657,15 @@ fn recreateStructType(
3644fn recreateUnionType(3657fn recreateUnionType(
3645 pt: Zcu.PerThread,3658 pt: Zcu.PerThread,
3646 old_ty: InternPool.Index,3659 old_ty: InternPool.Index,
3647 full_key: InternPool.Key.NamespaceType,3660 key: InternPool.Key.NamespaceType.Declared,
3648) Zcu.SemaError!InternPool.Index {3661) Allocator.Error!InternPool.Index {
3649 const zcu = pt.zcu;3662 const zcu = pt.zcu;
3650 const gpa = zcu.gpa;3663 const gpa = zcu.gpa;
3651 const ip = &zcu.intern_pool;3664 const ip = &zcu.intern_pool;
36523665
3653 const key = switch (full_key) {3666 const inst_info = key.zir_index.resolveFull(ip).?;
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;
3660 const file = zcu.fileByIndex(inst_info.file);3667 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
3662 const zir = file.zir;3669 const zir = file.zir;
36633670
3664 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);3671 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
...@@ -3681,7 +3688,7 @@ fn recreateUnionType(...@@ -3681,7 +3688,7 @@ fn recreateUnionType(
3681 break :blk fields_len;3688 break :blk fields_len;
3682 } else 0;3689 } 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
3686 const union_obj = ip.loadUnionType(old_ty);3693 const union_obj = ip.loadUnionType(old_ty);
36873694
...@@ -3731,23 +3738,19 @@ fn recreateUnionType(...@@ -3731,23 +3738,19 @@ fn recreateUnionType(
3731 return wip_ty.finish(ip, namespace_index);3738 return wip_ty.finish(ip, namespace_index);
3732}3739}
37333740
3741// TODO: is it safe for this to return `SemaError`? enum type resolution is a bit weird...
3734fn recreateEnumType(3742fn recreateEnumType(
3735 pt: Zcu.PerThread,3743 pt: Zcu.PerThread,
3736 old_ty: InternPool.Index,3744 old_ty: InternPool.Index,
3737 full_key: InternPool.Key.NamespaceType,3745 key: InternPool.Key.NamespaceType.Declared,
3738) Zcu.SemaError!InternPool.Index {3746) Zcu.SemaError!InternPool.Index {
3739 const zcu = pt.zcu;3747 const zcu = pt.zcu;
3740 const gpa = zcu.gpa;3748 const gpa = zcu.gpa;
3741 const ip = &zcu.intern_pool;3749 const ip = &zcu.intern_pool;
37423750
3743 const key = switch (full_key) {3751 const inst_info = key.zir_index.resolveFull(ip).?;
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;
3749 const file = zcu.fileByIndex(inst_info.file);3752 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
3751 const zir = file.zir;3754 const zir = file.zir;
37523755
3753 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);3756 assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended);
...@@ -3787,7 +3790,7 @@ fn recreateEnumType(...@@ -3787,7 +3790,7 @@ fn recreateEnumType(
3787 break :blk decls_len;3790 break :blk decls_len;
3788 } else 0;3791 } 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
3792 extra_index += captures_len;3795 extra_index += captures_len;
3793 extra_index += decls_len;3796 extra_index += decls_len;