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(...@@ -7077,6 +7077,7 @@ fn getOrPutKeyEnsuringAdditionalCapacity(
7077 const index = entry.acquire();7077 const index = entry.acquire();
7078 if (index == .none) break;7078 if (index == .none) break;
7079 if (entry.hash != hash) continue;7079 if (entry.hash != hash) continue;
7080 if (ip.isRemoved(index)) continue;
7080 if (ip.indexToKey(index).eql(key, ip)) return .{ .existing = index };7081 if (ip.indexToKey(index).eql(key, ip)) return .{ .existing = index };
7081 }7082 }
7082 shard.mutate.map.mutex.lock();7083 shard.mutate.map.mutex.lock();
...@@ -7151,6 +7152,43 @@ fn getOrPutKeyEnsuringAdditionalCapacity(...@@ -7151,6 +7152,43 @@ fn getOrPutKeyEnsuringAdditionalCapacity(
7151 .map_index = map_index,7152 .map_index = map_index,
7152 } };7153 } };
7153}7154}
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
7155pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) Allocator.Error!Index {7193pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) Allocator.Error!Index {
7156 var gop = try ip.getOrPutKey(gpa, tid, key);7194 var gop = try ip.getOrPutKey(gpa, tid, key);
...@@ -7990,8 +8028,11 @@ pub fn getUnionType(...@@ -7990,8 +8028,11 @@ pub fn getUnionType(
7990 gpa: Allocator,8028 gpa: Allocator,
7991 tid: Zcu.PerThread.Id,8029 tid: Zcu.PerThread.Id,
7992 ini: UnionTypeInit,8030 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,
7993) Allocator.Error!WipNamespaceType.Result {8034) 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) {
7995 .declared => |d| .{ .declared = .{8036 .declared => |d| .{ .declared = .{
7996 .zir_index = d.zir_index,8037 .zir_index = d.zir_index,
7997 .captures = .{ .external = d.captures },8038 .captures = .{ .external = d.captures },
...@@ -8000,7 +8041,11 @@ pub fn getUnionType(...@@ -8000,7 +8041,11 @@ pub fn getUnionType(
8000 .zir_index = r.zir_index,8041 .zir_index = r.zir_index,
8001 .type_hash = r.type_hash,8042 .type_hash = r.type_hash,
8002 } },8043 } },
8003 } });8044 } };
8045 var gop = if (replace_existing)
8046 ip.putKeyReplace(tid, key)
8047 else
8048 try ip.getOrPutKey(gpa, tid, key);
8004 defer gop.deinit();8049 defer gop.deinit();
8005 if (gop == .existing) return .{ .existing = gop.existing };8050 if (gop == .existing) return .{ .existing = gop.existing };
80068051
...@@ -8166,8 +8211,11 @@ pub fn getStructType(...@@ -8166,8 +8211,11 @@ pub fn getStructType(
8166 gpa: Allocator,8211 gpa: Allocator,
8167 tid: Zcu.PerThread.Id,8212 tid: Zcu.PerThread.Id,
8168 ini: StructTypeInit,8213 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,
8169) Allocator.Error!WipNamespaceType.Result {8217) 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) {
8171 .declared => |d| .{ .declared = .{8219 .declared => |d| .{ .declared = .{
8172 .zir_index = d.zir_index,8220 .zir_index = d.zir_index,
8173 .captures = .{ .external = d.captures },8221 .captures = .{ .external = d.captures },
...@@ -8176,7 +8224,11 @@ pub fn getStructType(...@@ -8176,7 +8224,11 @@ pub fn getStructType(
8176 .zir_index = r.zir_index,8224 .zir_index = r.zir_index,
8177 .type_hash = r.type_hash,8225 .type_hash = r.type_hash,
8178 } },8226 } },
8179 } });8227 } };
8228 var gop = if (replace_existing)
8229 ip.putKeyReplace(tid, key)
8230 else
8231 try ip.getOrPutKey(gpa, tid, key);
8180 defer gop.deinit();8232 defer gop.deinit();
8181 if (gop == .existing) return .{ .existing = gop.existing };8233 if (gop == .existing) return .{ .existing = gop.existing };
81828234
...@@ -9200,8 +9252,11 @@ pub fn getEnumType(...@@ -9200,8 +9252,11 @@ pub fn getEnumType(
9200 gpa: Allocator,9252 gpa: Allocator,
9201 tid: Zcu.PerThread.Id,9253 tid: Zcu.PerThread.Id,
9202 ini: EnumTypeInit,9254 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,
9203) Allocator.Error!WipEnumType.Result {9258) 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) {
9205 .declared => |d| .{ .declared = .{9260 .declared => |d| .{ .declared = .{
9206 .zir_index = d.zir_index,9261 .zir_index = d.zir_index,
9207 .captures = .{ .external = d.captures },9262 .captures = .{ .external = d.captures },
...@@ -9210,7 +9265,11 @@ pub fn getEnumType(...@@ -9210,7 +9265,11 @@ pub fn getEnumType(
9210 .zir_index = r.zir_index,9265 .zir_index = r.zir_index,
9211 .type_hash = r.type_hash,9266 .type_hash = r.type_hash,
9212 } },9267 } },
9213 } });9268 } };
9269 var gop = if (replace_existing)
9270 ip.putKeyReplace(tid, key)
9271 else
9272 try ip.getOrPutKey(gpa, tid, key);
9214 defer gop.deinit();9273 defer gop.deinit();
9215 if (gop == .existing) return .{ .existing = gop.existing };9274 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) {...@@ -2724,9 +2724,9 @@ fn wrapWipTy(sema: *Sema, wip_ty: anytype) @TypeOf(wip_ty) {
2724}2724}
27252725
2726/// Given a type just looked up in the `InternPool`, check whether it is2726/// 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 pool2727/// considered outdated on this update. If so, returns `true`, and the
2728/// and return `true`.2728/// caller must replace the outdated type with a fresh one.
2729fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {2729fn checkOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {
2730 const pt = sema.pt;2730 const pt = sema.pt;
2731 const zcu = pt.zcu;2731 const zcu = pt.zcu;
2732 const ip = &zcu.intern_pool;2732 const ip = &zcu.intern_pool;
...@@ -2745,7 +2745,6 @@ fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {...@@ -2745,7 +2745,6 @@ fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {
2745 if (!was_outdated) return false;2745 if (!was_outdated) return false;
2746 _ = zcu.outdated_ready.swapRemove(cau_unit);2746 _ = zcu.outdated_ready.swapRemove(cau_unit);
2747 zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, cau_unit);2747 zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, cau_unit);
2748 zcu.intern_pool.remove(pt.tid, ty);
2749 try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });2748 try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
2750 return true;2749 return true;
2751}2750}
...@@ -2815,14 +2814,14 @@ fn zirStructDecl(...@@ -2815,14 +2814,14 @@ fn zirStructDecl(
2815 .captures = captures,2814 .captures = captures,
2816 } },2815 } },
2817 };2816 };
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)) {
2819 .existing => |ty| wip: {2818 .existing => |ty| wip: {
2820 if (!try sema.maybeRemoveOutdatedType(ty)) {2819 if (!try sema.checkOutdatedType(ty)) {
2821 try sema.declareDependency(.{ .interned = ty });2820 try sema.declareDependency(.{ .interned = ty });
2822 try sema.addTypeReferenceEntry(src, ty);2821 try sema.addTypeReferenceEntry(src, ty);
2823 return Air.internedToRef(ty);2822 return Air.internedToRef(ty);
2824 }2823 }
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;
2826 },2825 },
2827 .wip => |wip| wip,2826 .wip => |wip| wip,
2828 });2827 });
...@@ -3041,14 +3040,14 @@ fn zirEnumDecl(...@@ -3041,14 +3040,14 @@ fn zirEnumDecl(
3041 .captures = captures,3040 .captures = captures,
3042 } },3041 } },
3043 };3042 };
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)) {
3045 .existing => |ty| wip: {3044 .existing => |ty| wip: {
3046 if (!try sema.maybeRemoveOutdatedType(ty)) {3045 if (!try sema.checkOutdatedType(ty)) {
3047 try sema.declareDependency(.{ .interned = ty });3046 try sema.declareDependency(.{ .interned = ty });
3048 try sema.addTypeReferenceEntry(src, ty);3047 try sema.addTypeReferenceEntry(src, ty);
3049 return Air.internedToRef(ty);3048 return Air.internedToRef(ty);
3050 }3049 }
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;
3052 },3051 },
3053 .wip => |wip| wip,3052 .wip => |wip| wip,
3054 });3053 });
...@@ -3311,14 +3310,14 @@ fn zirUnionDecl(...@@ -3311,14 +3310,14 @@ fn zirUnionDecl(
3311 .captures = captures,3310 .captures = captures,
3312 } },3311 } },
3313 };3312 };
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)) {
3315 .existing => |ty| wip: {3314 .existing => |ty| wip: {
3316 if (!try sema.maybeRemoveOutdatedType(ty)) {3315 if (!try sema.checkOutdatedType(ty)) {
3317 try sema.declareDependency(.{ .interned = ty });3316 try sema.declareDependency(.{ .interned = ty });
3318 try sema.addTypeReferenceEntry(src, ty);3317 try sema.addTypeReferenceEntry(src, ty);
3319 return Air.internedToRef(ty);3318 return Air.internedToRef(ty);
3320 }3319 }
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;
3322 },3321 },
3323 .wip => |wip| wip,3322 .wip => |wip| wip,
3324 });3323 });
...@@ -3407,7 +3406,7 @@ fn zirOpaqueDecl(...@@ -3407,7 +3406,7 @@ fn zirOpaqueDecl(
3407 };3406 };
3408 // No `wrapWipTy` needed as no std.builtin types are opaque.3407 // No `wrapWipTy` needed as no std.builtin types are opaque.
3409 const wip_ty = switch (try ip.getOpaqueType(gpa, pt.tid, opaque_init)) {3408 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.
3411 .existing => |ty| {3410 .existing => |ty| {
3412 try sema.addTypeReferenceEntry(src, ty);3411 try sema.addTypeReferenceEntry(src, ty);
3413 return Air.internedToRef(ty);3412 return Air.internedToRef(ty);
...@@ -22054,7 +22053,7 @@ fn reifyEnum(...@@ -22054,7 +22053,7 @@ fn reifyEnum(
22054 .zir_index = tracked_inst,22053 .zir_index = tracked_inst,
22055 .type_hash = hasher.final(),22054 .type_hash = hasher.final(),
22056 } },22055 } },
22057 })) {22056 }, false)) {
22058 .wip => |wip| wip,22057 .wip => |wip| wip,
22059 .existing => |ty| {22058 .existing => |ty| {
22060 try sema.declareDependency(.{ .interned = ty });22059 try sema.declareDependency(.{ .interned = ty });
...@@ -22224,7 +22223,7 @@ fn reifyUnion(...@@ -22224,7 +22223,7 @@ fn reifyUnion(
22224 .zir_index = tracked_inst,22223 .zir_index = tracked_inst,
22225 .type_hash = hasher.final(),22224 .type_hash = hasher.final(),
22226 } },22225 } },
22227 })) {22226 }, false)) {
22228 .wip => |wip| wip,22227 .wip => |wip| wip,
22229 .existing => |ty| {22228 .existing => |ty| {
22230 try sema.declareDependency(.{ .interned = ty });22229 try sema.declareDependency(.{ .interned = ty });
...@@ -22494,7 +22493,7 @@ fn reifyStruct(...@@ -22494,7 +22493,7 @@ fn reifyStruct(
22494 .zir_index = tracked_inst,22493 .zir_index = tracked_inst,
22495 .type_hash = hasher.final(),22494 .type_hash = hasher.final(),
22496 } },22495 } },
22497 })) {22496 }, false)) {
22498 .wip => |wip| wip,22497 .wip => |wip| wip,
22499 .existing => |ty| {22498 .existing => |ty| {
22500 try sema.declareDependency(.{ .interned = ty });22499 try sema.declareDependency(.{ .interned = ty });
src/Zcu/PerThread.zig+6-9
...@@ -925,6 +925,7 @@ fn createFileRootStruct(...@@ -925,6 +925,7 @@ fn createFileRootStruct(
925 pt: Zcu.PerThread,925 pt: Zcu.PerThread,
926 file_index: Zcu.File.Index,926 file_index: Zcu.File.Index,
927 namespace_index: Zcu.Namespace.Index,927 namespace_index: Zcu.Namespace.Index,
928 replace_existing: bool,
928) Allocator.Error!InternPool.Index {929) Allocator.Error!InternPool.Index {
929 const zcu = pt.zcu;930 const zcu = pt.zcu;
930 const gpa = zcu.gpa;931 const gpa = zcu.gpa;
...@@ -968,7 +969,7 @@ fn createFileRootStruct(...@@ -968,7 +969,7 @@ fn createFileRootStruct(
968 .zir_index = tracked_inst,969 .zir_index = tracked_inst,
969 .captures = &.{},970 .captures = &.{},
970 } },971 } },
971 })) {972 }, replace_existing)) {
972 .existing => unreachable, // we wouldn't be analysing the file root if this type existed973 .existing => unreachable, // we wouldn't be analysing the file root if this type existed
973 .wip => |wip| wip,974 .wip => |wip| wip,
974 };975 };
...@@ -1023,8 +1024,7 @@ fn recreateFileRoot(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError...@@ -1023,8 +1024,7 @@ fn recreateFileRoot(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError
1023 zcu.gpa,1024 zcu.gpa,
1024 InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }),1025 InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }),
1025 );1026 );
1026 ip.remove(pt.tid, file_root_type);1027 _ = try pt.createFileRootStruct(file_index, namespace_index, true);
1027 _ = try pt.createFileRootStruct(file_index, namespace_index);
1028}1028}
10291029
1030/// Re-scan the namespace of a file's root struct type on an incremental update.1030/// 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....@@ -1062,8 +1062,6 @@ fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.
1062 try pt.scanNamespace(namespace_index, decls);1062 try pt.scanNamespace(namespace_index, decls);
1063}1063}
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.
1067fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {1065fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
1068 const tracy = trace(@src());1066 const tracy = trace(@src());
1069 defer tracy.end();1067 defer tracy.end();
...@@ -1083,7 +1081,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {...@@ -1083,7 +1081,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
1083 .owner_type = undefined, // set in `createFileRootStruct`1081 .owner_type = undefined, // set in `createFileRootStruct`
1084 .file_scope = file_index,1082 .file_scope = file_index,
1085 });1083 });
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);
1087 errdefer zcu.intern_pool.remove(pt.tid, struct_ty);1085 errdefer zcu.intern_pool.remove(pt.tid, struct_ty);
10881086
1089 switch (zcu.comp.cache_use) {1087 switch (zcu.comp.cache_use) {
...@@ -1153,11 +1151,10 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {...@@ -1153,11 +1151,10 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
1153 // This declaration has no value so is definitely not a std.builtin type.1151 // This declaration has no value so is definitely not a std.builtin type.
1154 break :ip_index .none;1152 break :ip_index .none;
1155 },1153 },
1156 .type => |ty| {1154 .type => {
1157 // This is an incremental update, and this type is being re-analyzed because it is outdated.1155 // This is an incremental update, and this type is being re-analyzed because it is outdated.
1158 // The type must be recreated at a new `InternPool.Index`.1156 // 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.1157 // Mark it outdated so that creation sites are re-analyzed.
1160 ip.remove(pt.tid, ty);
1161 return .{1158 return .{
1162 .invalidate_decl_val = true,1159 .invalidate_decl_val = true,
1163 .invalidate_decl_ref = true,1160 .invalidate_decl_ref = true,