Skip to content

Commit f818098

Browse files
committed
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.
1 parent 814491f commit f818098

File tree

5 files changed

+96
-62
lines changed

5 files changed

+96
-62
lines changed

lib/std/zig/Zir.zig

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
5070+
pub 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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
35303532
pub 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,31 +3574,25 @@ 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
}
35643583

35653584
fn 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(
36443657
fn 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...
37343742
fn 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;

0 commit comments

Comments
 (0)