authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-08-14 08:10:49+01:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-08-17 18:50:10-04:00
log46388d338a93a35d139866411f80115a03b30a6a
tree57cd962b1fec235a99b808d2a2877bc4bcbd5f05
parent978fe68a65be2b5a1551ab5eafdcdbfa467ba891

InternPool: don't remove outdated types

When a type becomes outdated, there will still be lingering references to the old index -- for instance, any declaration whose value was that type holds a reference to that index. These references may live for an arbitrarily long time in some cases. So, we can't just remove the type from the pool -- the old `Index` must remain valid! Instead, we want to preserve the old `Index`, but avoid it from ever appearing in lookups. (It's okay if analysis of something referencing the old `Index` does weird stuff -- such analysis are guaranteed by the incremental compilation model to always be unreferenced.) So, we use the new `InternPool.putKeyReplace` to replace the shard entry for this index with the newly-created index.

3 files changed, 87 insertions(+), 32 deletions(-)

src/InternPool.zig+65-6
......@@ -7077,6 +7077,7 @@ fn getOrPutKeyEnsuringAdditionalCapacity(
70777077 const index = entry.acquire();
70787078 if (index == .none) break;
70797079 if (entry.hash != hash) continue;
7080 if (ip.isRemoved(index)) continue;
70807081 if (ip.indexToKey(index).eql(key, ip)) return .{ .existing = index };
70817082 }
70827083 shard.mutate.map.mutex.lock();
......@@ -7151,6 +7152,43 @@ fn getOrPutKeyEnsuringAdditionalCapacity(
71517152 .map_index = map_index,
71527153 } };
71537154}
7155/// Like `getOrPutKey`, but asserts that the key already exists, and prepares to replace
7156/// its shard entry with a new `Index` anyway. After finalizing this, the old index remains
7157/// valid (in that `indexToKey` and similar queries will behave as before), but it will
7158/// never be returned from a lookup (`getOrPutKey` etc).
7159/// This is used by incremental compilation when an existing container type is outdated. In
7160/// this case, the type must be recreated at a new `InternPool.Index`, but the old index must
7161/// remain valid since now-unreferenced `AnalUnit`s may retain references to it. The old index
7162/// will be cleaned up when the `Zcu` undergoes garbage collection.
7163fn putKeyReplace(
7164 ip: *InternPool,
7165 tid: Zcu.PerThread.Id,
7166 key: Key,
7167) GetOrPutKey {
7168 const full_hash = key.hash64(ip);
7169 const hash: u32 = @truncate(full_hash >> 32);
7170 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
7171 shard.mutate.map.mutex.lock();
7172 errdefer shard.mutate.map.mutex.unlock();
7173 const map = shard.shared.map;
7174 const map_mask = map.header().mask();
7175 var map_index = hash;
7176 while (true) : (map_index += 1) {
7177 map_index &= map_mask;
7178 const entry = &map.entries[map_index];
7179 const index = entry.value;
7180 assert(index != .none); // key not present
7181 if (entry.hash == hash and ip.indexToKey(index).eql(key, ip)) {
7182 break; // we found the entry to replace
7183 }
7184 }
7185 return .{ .new = .{
7186 .ip = ip,
7187 .tid = tid,
7188 .shard = shard,
7189 .map_index = map_index,
7190 } };
7191}
71547192
71557193pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) Allocator.Error!Index {
71567194 var gop = try ip.getOrPutKey(gpa, tid, key);
......@@ -7990,8 +8028,11 @@ pub fn getUnionType(
79908028 gpa: Allocator,
79918029 tid: Zcu.PerThread.Id,
79928030 ini: UnionTypeInit,
8031 /// If it is known that there is an existing type with this key which is outdated,
8032 /// this is passed as `true`, and the type is replaced with one at a fresh index.
8033 replace_existing: bool,
79938034) Allocator.Error!WipNamespaceType.Result {
7994 var gop = try ip.getOrPutKey(gpa, tid, .{ .union_type = switch (ini.key) {
8035 const key: Key = .{ .union_type = switch (ini.key) {
79958036 .declared => |d| .{ .declared = .{
79968037 .zir_index = d.zir_index,
79978038 .captures = .{ .external = d.captures },
......@@ -8000,7 +8041,11 @@ pub fn getUnionType(
80008041 .zir_index = r.zir_index,
80018042 .type_hash = r.type_hash,
80028043 } },
8003 } });
8044 } };
8045 var gop = if (replace_existing)
8046 ip.putKeyReplace(tid, key)
8047 else
8048 try ip.getOrPutKey(gpa, tid, key);
80048049 defer gop.deinit();
80058050 if (gop == .existing) return .{ .existing = gop.existing };
80068051
......@@ -8166,8 +8211,11 @@ pub fn getStructType(
81668211 gpa: Allocator,
81678212 tid: Zcu.PerThread.Id,
81688213 ini: StructTypeInit,
8214 /// If it is known that there is an existing type with this key which is outdated,
8215 /// this is passed as `true`, and the type is replaced with one at a fresh index.
8216 replace_existing: bool,
81698217) Allocator.Error!WipNamespaceType.Result {
8170 var gop = try ip.getOrPutKey(gpa, tid, .{ .struct_type = switch (ini.key) {
8218 const key: Key = .{ .struct_type = switch (ini.key) {
81718219 .declared => |d| .{ .declared = .{
81728220 .zir_index = d.zir_index,
81738221 .captures = .{ .external = d.captures },
......@@ -8176,7 +8224,11 @@ pub fn getStructType(
81768224 .zir_index = r.zir_index,
81778225 .type_hash = r.type_hash,
81788226 } },
8179 } });
8227 } };
8228 var gop = if (replace_existing)
8229 ip.putKeyReplace(tid, key)
8230 else
8231 try ip.getOrPutKey(gpa, tid, key);
81808232 defer gop.deinit();
81818233 if (gop == .existing) return .{ .existing = gop.existing };
81828234
......@@ -9200,8 +9252,11 @@ pub fn getEnumType(
92009252 gpa: Allocator,
92019253 tid: Zcu.PerThread.Id,
92029254 ini: EnumTypeInit,
9255 /// If it is known that there is an existing type with this key which is outdated,
9256 /// this is passed as `true`, and the type is replaced with one at a fresh index.
9257 replace_existing: bool,
92039258) Allocator.Error!WipEnumType.Result {
9204 var gop = try ip.getOrPutKey(gpa, tid, .{ .enum_type = switch (ini.key) {
9259 const key: Key = .{ .enum_type = switch (ini.key) {
92059260 .declared => |d| .{ .declared = .{
92069261 .zir_index = d.zir_index,
92079262 .captures = .{ .external = d.captures },
......@@ -9210,7 +9265,11 @@ pub fn getEnumType(
92109265 .zir_index = r.zir_index,
92119266 .type_hash = r.type_hash,
92129267 } },
9213 } });
9268 } };
9269 var gop = if (replace_existing)
9270 ip.putKeyReplace(tid, key)
9271 else
9272 try ip.getOrPutKey(gpa, tid, key);
92149273 defer gop.deinit();
92159274 if (gop == .existing) return .{ .existing = gop.existing };
92169275
src/Sema.zig+16-17
......@@ -2724,9 +2724,9 @@ fn wrapWipTy(sema: *Sema, wip_ty: anytype) @TypeOf(wip_ty) {
27242724}
27252725
27262726/// Given a type just looked up in the `InternPool`, check whether it is
2727/// considered outdated on this update. If so, remove it from the pool
2728/// and return `true`.
2729fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {
2727/// considered outdated on this update. If so, returns `true`, and the
2728/// caller must replace the outdated type with a fresh one.
2729fn checkOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {
27302730 const pt = sema.pt;
27312731 const zcu = pt.zcu;
27322732 const ip = &zcu.intern_pool;
......@@ -2745,7 +2745,6 @@ fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {
27452745 if (!was_outdated) return false;
27462746 _ = zcu.outdated_ready.swapRemove(cau_unit);
27472747 zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, cau_unit);
2748 zcu.intern_pool.remove(pt.tid, ty);
27492748 try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
27502749 return true;
27512750}
......@@ -2815,14 +2814,14 @@ fn zirStructDecl(
28152814 .captures = captures,
28162815 } },
28172816 };
2818 const wip_ty = sema.wrapWipTy(switch (try ip.getStructType(gpa, pt.tid, struct_init)) {
2817 const wip_ty = sema.wrapWipTy(switch (try ip.getStructType(gpa, pt.tid, struct_init, false)) {
28192818 .existing => |ty| wip: {
2820 if (!try sema.maybeRemoveOutdatedType(ty)) {
2819 if (!try sema.checkOutdatedType(ty)) {
28212820 try sema.declareDependency(.{ .interned = ty });
28222821 try sema.addTypeReferenceEntry(src, ty);
28232822 return Air.internedToRef(ty);
28242823 }
2825 break :wip (try ip.getStructType(gpa, pt.tid, struct_init)).wip;
2824 break :wip (try ip.getStructType(gpa, pt.tid, struct_init, true)).wip;
28262825 },
28272826 .wip => |wip| wip,
28282827 });
......@@ -3041,14 +3040,14 @@ fn zirEnumDecl(
30413040 .captures = captures,
30423041 } },
30433042 };
3044 const wip_ty = sema.wrapWipTy(switch (try ip.getEnumType(gpa, pt.tid, enum_init)) {
3043 const wip_ty = sema.wrapWipTy(switch (try ip.getEnumType(gpa, pt.tid, enum_init, false)) {
30453044 .existing => |ty| wip: {
3046 if (!try sema.maybeRemoveOutdatedType(ty)) {
3045 if (!try sema.checkOutdatedType(ty)) {
30473046 try sema.declareDependency(.{ .interned = ty });
30483047 try sema.addTypeReferenceEntry(src, ty);
30493048 return Air.internedToRef(ty);
30503049 }
3051 break :wip (try ip.getEnumType(gpa, pt.tid, enum_init)).wip;
3050 break :wip (try ip.getEnumType(gpa, pt.tid, enum_init, true)).wip;
30523051 },
30533052 .wip => |wip| wip,
30543053 });
......@@ -3311,14 +3310,14 @@ fn zirUnionDecl(
33113310 .captures = captures,
33123311 } },
33133312 };
3314 const wip_ty = sema.wrapWipTy(switch (try ip.getUnionType(gpa, pt.tid, union_init)) {
3313 const wip_ty = sema.wrapWipTy(switch (try ip.getUnionType(gpa, pt.tid, union_init, false)) {
33153314 .existing => |ty| wip: {
3316 if (!try sema.maybeRemoveOutdatedType(ty)) {
3315 if (!try sema.checkOutdatedType(ty)) {
33173316 try sema.declareDependency(.{ .interned = ty });
33183317 try sema.addTypeReferenceEntry(src, ty);
33193318 return Air.internedToRef(ty);
33203319 }
3321 break :wip (try ip.getUnionType(gpa, pt.tid, union_init)).wip;
3320 break :wip (try ip.getUnionType(gpa, pt.tid, union_init, true)).wip;
33223321 },
33233322 .wip => |wip| wip,
33243323 });
......@@ -3407,7 +3406,7 @@ fn zirOpaqueDecl(
34073406 };
34083407 // No `wrapWipTy` needed as no std.builtin types are opaque.
34093408 const wip_ty = switch (try ip.getOpaqueType(gpa, pt.tid, opaque_init)) {
3410 // No `maybeRemoveOutdatedType` as opaque types are never outdated.
3409 // No `checkOutdatedType` as opaque types are never outdated.
34113410 .existing => |ty| {
34123411 try sema.addTypeReferenceEntry(src, ty);
34133412 return Air.internedToRef(ty);
......@@ -22054,7 +22053,7 @@ fn reifyEnum(
2205422053 .zir_index = tracked_inst,
2205522054 .type_hash = hasher.final(),
2205622055 } },
22057 })) {
22056 }, false)) {
2205822057 .wip => |wip| wip,
2205922058 .existing => |ty| {
2206022059 try sema.declareDependency(.{ .interned = ty });
......@@ -22224,7 +22223,7 @@ fn reifyUnion(
2222422223 .zir_index = tracked_inst,
2222522224 .type_hash = hasher.final(),
2222622225 } },
22227 })) {
22226 }, false)) {
2222822227 .wip => |wip| wip,
2222922228 .existing => |ty| {
2223022229 try sema.declareDependency(.{ .interned = ty });
......@@ -22494,7 +22493,7 @@ fn reifyStruct(
2249422493 .zir_index = tracked_inst,
2249522494 .type_hash = hasher.final(),
2249622495 } },
22497 })) {
22496 }, false)) {
2249822497 .wip => |wip| wip,
2249922498 .existing => |ty| {
2250022499 try sema.declareDependency(.{ .interned = ty });
src/Zcu/PerThread.zig+6-9
......@@ -925,6 +925,7 @@ fn createFileRootStruct(
925925 pt: Zcu.PerThread,
926926 file_index: Zcu.File.Index,
927927 namespace_index: Zcu.Namespace.Index,
928 replace_existing: bool,
928929) Allocator.Error!InternPool.Index {
929930 const zcu = pt.zcu;
930931 const gpa = zcu.gpa;
......@@ -968,7 +969,7 @@ fn createFileRootStruct(
968969 .zir_index = tracked_inst,
969970 .captures = &.{},
970971 } },
971 })) {
972 }, replace_existing)) {
972973 .existing => unreachable, // we wouldn't be analysing the file root if this type existed
973974 .wip => |wip| wip,
974975 };
......@@ -1023,8 +1024,7 @@ fn recreateFileRoot(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError
10231024 zcu.gpa,
10241025 InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }),
10251026 );
1026 ip.remove(pt.tid, file_root_type);
1027 _ = try pt.createFileRootStruct(file_index, namespace_index);
1027 _ = try pt.createFileRootStruct(file_index, namespace_index, true);
10281028}
10291029
10301030/// Re-scan the namespace of a file's root struct type on an incremental update.
......@@ -1062,8 +1062,6 @@ fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.
10621062 try pt.scanNamespace(namespace_index, decls);
10631063}
10641064
1065/// Regardless of the file status, will create a `Decl` if none exists so that we can track
1066/// dependencies and re-analyze when the file becomes outdated.
10671065fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
10681066 const tracy = trace(@src());
10691067 defer tracy.end();
......@@ -1083,7 +1081,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
10831081 .owner_type = undefined, // set in `createFileRootStruct`
10841082 .file_scope = file_index,
10851083 });
1086 const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index);
1084 const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false);
10871085 errdefer zcu.intern_pool.remove(pt.tid, struct_ty);
10881086
10891087 switch (zcu.comp.cache_use) {
......@@ -1153,11 +1151,10 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
11531151 // This declaration has no value so is definitely not a std.builtin type.
11541152 break :ip_index .none;
11551153 },
1156 .type => |ty| {
1154 .type => {
11571155 // This is an incremental update, and this type is being re-analyzed because it is outdated.
11581156 // The type must be recreated at a new `InternPool.Index`.
1159 // Remove it from the InternPool and mark it outdated so that creation sites are re-analyzed.
1160 ip.remove(pt.tid, ty);
1157 // Mark it outdated so that creation sites are re-analyzed.
11611158 return .{
11621159 .invalidate_decl_val = true,
11631160 .invalidate_decl_ref = true,