authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-08-11 23:16:06+01:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-08-17 18:50:10-04:00
log895267c916f874593b0788b198b7de140b6b335b
treecd1256cc5f48f6bdbc1283bf19bb0f224f45d293
parent2b05e85107dd1c637ab40f8b145b232d18e8d6c6

frontend: incremental progress

This commit makes more progress towards incremental compilation, fixing some crashes in the frontend. Notably, it fixes the regressions introduced by #20964. It also cleans up the "outdated file root" mechanism, by virtue of deleting it: we now detect outdated file roots just after updating ZIR refs, and re-scan their namespaces.

9 files changed, 410 insertions(+), 305 deletions(-)

src/Compilation.zig+24-16
......@@ -3081,7 +3081,7 @@ pub fn totalErrorCount(comp: *Compilation) u32 {
30813081 for (zcu.failed_analysis.keys()) |anal_unit| {
30823082 const file_index = switch (anal_unit.unwrap()) {
30833083 .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope,
3084 .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip).file,
3084 .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file,
30853085 };
30863086 if (zcu.fileByIndex(file_index).okToReportErrors()) {
30873087 total += 1;
......@@ -3091,11 +3091,13 @@ pub fn totalErrorCount(comp: *Compilation) u32 {
30913091 }
30923092 }
30933093
3094 if (zcu.intern_pool.global_error_set.getNamesFromMainThread().len > zcu.error_limit) {
3095 total += 1;
3094 for (zcu.failed_codegen.keys()) |nav| {
3095 if (zcu.navFileScope(nav).okToReportErrors()) {
3096 total += 1;
3097 }
30963098 }
30973099
3098 for (zcu.failed_codegen.keys()) |_| {
3100 if (zcu.intern_pool.global_error_set.getNamesFromMainThread().len > zcu.error_limit) {
30993101 total += 1;
31003102 }
31013103 }
......@@ -3114,7 +3116,13 @@ pub fn totalErrorCount(comp: *Compilation) u32 {
31143116 }
31153117 }
31163118
3117 return @as(u32, @intCast(total));
3119 if (comp.module) |zcu| {
3120 if (total == 0 and zcu.transitive_failed_analysis.count() > 0) {
3121 @panic("Transitive analysis errors, but none actually emitted");
3122 }
3123 }
3124
3125 return @intCast(total);
31183126}
31193127
31203128/// This function is temporally single-threaded.
......@@ -3214,7 +3222,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
32143222 for (zcu.failed_analysis.keys(), zcu.failed_analysis.values()) |anal_unit, error_msg| {
32153223 const file_index = switch (anal_unit.unwrap()) {
32163224 .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope,
3217 .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip).file,
3225 .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file,
32183226 };
32193227
32203228 // Skip errors for AnalUnits within files that had a parse failure.
......@@ -3243,7 +3251,8 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
32433251 }
32443252 }
32453253 }
3246 for (zcu.failed_codegen.values()) |error_msg| {
3254 for (zcu.failed_codegen.keys(), zcu.failed_codegen.values()) |nav, error_msg| {
3255 if (!zcu.navFileScope(nav).okToReportErrors()) continue;
32473256 try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
32483257 }
32493258 for (zcu.failed_exports.values()) |value| {
......@@ -3608,10 +3617,9 @@ fn performAllTheWorkInner(
36083617 // Pre-load these things from our single-threaded context since they
36093618 // will be needed by the worker threads.
36103619 const path_digest = zcu.filePathDigest(file_index);
3611 const old_root_type = zcu.fileRootType(file_index);
36123620 const file = zcu.fileByIndex(file_index);
36133621 comp.thread_pool.spawnWgId(&astgen_wait_group, workerAstGenFile, .{
3614 comp, file, file_index, path_digest, old_root_type, zir_prog_node, &astgen_wait_group, .root,
3622 comp, file, file_index, path_digest, zir_prog_node, &astgen_wait_group, .root,
36153623 });
36163624 }
36173625 }
......@@ -3649,6 +3657,7 @@ fn performAllTheWorkInner(
36493657 }
36503658 try reportMultiModuleErrors(pt);
36513659 try zcu.flushRetryableFailures();
3660
36523661 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
36533662 zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
36543663 }
......@@ -4283,7 +4292,6 @@ fn workerAstGenFile(
42834292 file: *Zcu.File,
42844293 file_index: Zcu.File.Index,
42854294 path_digest: Cache.BinDigest,
4286 old_root_type: InternPool.Index,
42874295 prog_node: std.Progress.Node,
42884296 wg: *WaitGroup,
42894297 src: Zcu.AstGenSrc,
......@@ -4292,7 +4300,7 @@ fn workerAstGenFile(
42924300 defer child_prog_node.end();
42934301
42944302 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
4295 pt.astGenFile(file, path_digest, old_root_type) catch |err| switch (err) {
4303 pt.astGenFile(file, path_digest) catch |err| switch (err) {
42964304 error.AnalysisFail => return,
42974305 else => {
42984306 file.status = .retryable_failure;
......@@ -4323,7 +4331,7 @@ fn workerAstGenFile(
43234331 // `@import("builtin")` is handled specially.
43244332 if (mem.eql(u8, import_path, "builtin")) continue;
43254333
4326 const import_result, const imported_path_digest, const imported_root_type = blk: {
4334 const import_result, const imported_path_digest = blk: {
43274335 comp.mutex.lock();
43284336 defer comp.mutex.unlock();
43294337
......@@ -4338,8 +4346,7 @@ fn workerAstGenFile(
43384346 comp.appendFileSystemInput(fsi, res.file.mod.root, res.file.sub_file_path) catch continue;
43394347 };
43404348 const imported_path_digest = pt.zcu.filePathDigest(res.file_index);
4341 const imported_root_type = pt.zcu.fileRootType(res.file_index);
4342 break :blk .{ res, imported_path_digest, imported_root_type };
4349 break :blk .{ res, imported_path_digest };
43434350 };
43444351 if (import_result.is_new) {
43454352 log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{
......@@ -4350,7 +4357,7 @@ fn workerAstGenFile(
43504357 .import_tok = item.data.token,
43514358 } };
43524359 comp.thread_pool.spawnWgId(wg, workerAstGenFile, .{
4353 comp, import_result.file, import_result.file_index, imported_path_digest, imported_root_type, prog_node, wg, sub_src,
4360 comp, import_result.file, import_result.file_index, imported_path_digest, prog_node, wg, sub_src,
43544361 });
43554362 }
43564363 }
......@@ -6443,7 +6450,8 @@ fn buildOutputFromZig(
64436450
64446451 try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node);
64456452
6446 assert(out.* == null);
6453 // Under incremental compilation, `out` may already be populated from a prior update.
6454 assert(out.* == null or comp.incremental);
64476455 out.* = try sub_compilation.toCrtFile();
64486456}
64496457
src/InternPool.zig+135-17
......@@ -65,19 +65,49 @@ pub const single_threaded = builtin.single_threaded or !want_multi_threaded;
6565pub const TrackedInst = extern struct {
6666 file: FileIndex,
6767 inst: Zir.Inst.Index,
68 comptime {
69 // The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`.
70 assert(@sizeOf(@This()) == @sizeOf(FileIndex) + @sizeOf(Zir.Inst.Index));
71 }
68
69 pub const MaybeLost = extern struct {
70 file: FileIndex,
71 inst: ZirIndex,
72 pub const ZirIndex = enum(u32) {
73 /// Tracking failed for this ZIR instruction. Uses of it should fail.
74 lost = std.math.maxInt(u32),
75 _,
76 pub fn unwrap(inst: ZirIndex) ?Zir.Inst.Index {
77 return switch (inst) {
78 .lost => null,
79 _ => @enumFromInt(@intFromEnum(inst)),
80 };
81 }
82 pub fn wrap(inst: Zir.Inst.Index) ZirIndex {
83 return @enumFromInt(@intFromEnum(inst));
84 }
85 };
86 comptime {
87 // The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`.
88 assert(@sizeOf(@This()) == @sizeOf(FileIndex) + @sizeOf(ZirIndex));
89 }
90 };
91
7292 pub const Index = enum(u32) {
7393 _,
74 pub fn resolveFull(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) TrackedInst {
94 pub fn resolveFull(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) ?TrackedInst {
7595 const tracked_inst_unwrapped = tracked_inst_index.unwrap(ip);
7696 const tracked_insts = ip.getLocalShared(tracked_inst_unwrapped.tid).tracked_insts.acquire();
77 return tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index];
97 const maybe_lost = tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index];
98 return .{
99 .file = maybe_lost.file,
100 .inst = maybe_lost.inst.unwrap() orelse return null,
101 };
78102 }
79 pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) Zir.Inst.Index {
80 return i.resolveFull(ip).inst;
103 pub fn resolveFile(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) FileIndex {
104 const tracked_inst_unwrapped = tracked_inst_index.unwrap(ip);
105 const tracked_insts = ip.getLocalShared(tracked_inst_unwrapped.tid).tracked_insts.acquire();
106 const maybe_lost = tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index];
107 return maybe_lost.file;
108 }
109 pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) ?Zir.Inst.Index {
110 return (i.resolveFull(ip) orelse return null).inst;
81111 }
82112
83113 pub fn toOptional(i: TrackedInst.Index) Optional {
......@@ -120,7 +150,11 @@ pub fn trackZir(
120150 tid: Zcu.PerThread.Id,
121151 key: TrackedInst,
122152) Allocator.Error!TrackedInst.Index {
123 const full_hash = Hash.hash(0, std.mem.asBytes(&key));
153 const maybe_lost_key: TrackedInst.MaybeLost = .{
154 .file = key.file,
155 .inst = TrackedInst.MaybeLost.ZirIndex.wrap(key.inst),
156 };
157 const full_hash = Hash.hash(0, std.mem.asBytes(&maybe_lost_key));
124158 const hash: u32 = @truncate(full_hash >> 32);
125159 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
126160 var map = shard.shared.tracked_inst_map.acquire();
......@@ -132,12 +166,11 @@ pub fn trackZir(
132166 const entry = &map.entries[map_index];
133167 const index = entry.acquire().unwrap() orelse break;
134168 if (entry.hash != hash) continue;
135 if (std.meta.eql(index.resolveFull(ip), key)) return index;
169 if (std.meta.eql(index.resolveFull(ip) orelse continue, key)) return index;
136170 }
137171 shard.mutate.tracked_inst_map.mutex.lock();
138172 defer shard.mutate.tracked_inst_map.mutex.unlock();
139173 if (map.entries != shard.shared.tracked_inst_map.entries) {
140 shard.mutate.tracked_inst_map.len += 1;
141174 map = shard.shared.tracked_inst_map;
142175 map_mask = map.header().mask();
143176 map_index = hash;
......@@ -147,7 +180,7 @@ pub fn trackZir(
147180 const entry = &map.entries[map_index];
148181 const index = entry.acquire().unwrap() orelse break;
149182 if (entry.hash != hash) continue;
150 if (std.meta.eql(index.resolveFull(ip), key)) return index;
183 if (std.meta.eql(index.resolveFull(ip) orelse continue, key)) return index;
151184 }
152185 defer shard.mutate.tracked_inst_map.len += 1;
153186 const local = ip.getLocal(tid);
......@@ -161,7 +194,7 @@ pub fn trackZir(
161194 .tid = tid,
162195 .index = list.mutate.len,
163196 }).wrap(ip);
164 list.appendAssumeCapacity(.{key});
197 list.appendAssumeCapacity(.{maybe_lost_key});
165198 entry.release(index.toOptional());
166199 return index;
167200 }
......@@ -205,12 +238,91 @@ pub fn trackZir(
205238 .tid = tid,
206239 .index = list.mutate.len,
207240 }).wrap(ip);
208 list.appendAssumeCapacity(.{key});
241 list.appendAssumeCapacity(.{maybe_lost_key});
209242 map.entries[map_index] = .{ .value = index.toOptional(), .hash = hash };
210243 shard.shared.tracked_inst_map.release(new_map);
211244 return index;
212245}
213246
247pub fn rehashTrackedInsts(
248 ip: *InternPool,
249 gpa: Allocator,
250 /// TODO: maybe don't take this? it doesn't actually matter, only one thread is running at this point
251 tid: Zcu.PerThread.Id,
252) Allocator.Error!void {
253 // TODO: this function doesn't handle OOM well. What should it do?
254 // Indeed, what should anyone do when they run out of memory?
255
256 // We don't lock anything, as this function assumes that no other thread is
257 // accessing `tracked_insts`. This is necessary because we're going to be
258 // iterating the `TrackedInst`s in each `Local`, so we have to know that
259 // none will be added as we work.
260
261 // Figure out how big each shard need to be and store it in its mutate `len`.
262 for (ip.shards) |*shard| shard.mutate.tracked_inst_map.len = 0;
263 for (ip.locals) |*local| {
264 // `getMutableTrackedInsts` is okay only because no other thread is currently active.
265 // We need the `mutate` for the len.
266 for (local.getMutableTrackedInsts(gpa).viewAllowEmpty().items(.@"0")) |tracked_inst| {
267 if (tracked_inst.inst == .lost) continue; // we can ignore this one!
268 const full_hash = Hash.hash(0, std.mem.asBytes(&tracked_inst));
269 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
270 shard.mutate.tracked_inst_map.len += 1;
271 }
272 }
273
274 const Map = Shard.Map(TrackedInst.Index.Optional);
275
276 const arena_state = &ip.getLocal(tid).mutate.arena;
277
278 // We know how big each shard must be, so ensure we have the capacity we need.
279 for (ip.shards) |*shard| {
280 const want_capacity = std.math.ceilPowerOfTwo(u32, shard.mutate.tracked_inst_map.len * 5 / 3) catch unreachable;
281 const have_capacity = shard.shared.tracked_inst_map.header().capacity; // no acquire because we hold the mutex
282 if (have_capacity >= want_capacity) {
283 @memset(shard.shared.tracked_inst_map.entries[0..have_capacity], .{ .value = .none, .hash = undefined });
284 continue;
285 }
286 var arena = arena_state.promote(gpa);
287 defer arena_state.* = arena.state;
288 const new_map_buf = try arena.allocator().alignedAlloc(
289 u8,
290 Map.alignment,
291 Map.entries_offset + want_capacity * @sizeOf(Map.Entry),
292 );
293 const new_map: Map = .{ .entries = @ptrCast(new_map_buf[Map.entries_offset..].ptr) };
294 new_map.header().* = .{ .capacity = want_capacity };
295 @memset(new_map.entries[0..want_capacity], .{ .value = .none, .hash = undefined });
296 shard.shared.tracked_inst_map.release(new_map);
297 }
298
299 // Now, actually insert the items.
300 for (ip.locals, 0..) |*local, local_tid| {
301 // `getMutableTrackedInsts` is okay only because no other thread is currently active.
302 // We need the `mutate` for the len.
303 for (local.getMutableTrackedInsts(gpa).viewAllowEmpty().items(.@"0"), 0..) |tracked_inst, local_inst_index| {
304 if (tracked_inst.inst == .lost) continue; // we can ignore this one!
305 const full_hash = Hash.hash(0, std.mem.asBytes(&tracked_inst));
306 const hash: u32 = @truncate(full_hash >> 32);
307 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
308 const map = shard.shared.tracked_inst_map; // no acquire because we hold the mutex
309 const map_mask = map.header().mask();
310 var map_index = hash;
311 const entry = while (true) : (map_index += 1) {
312 map_index &= map_mask;
313 const entry = &map.entries[map_index];
314 if (entry.acquire() == .none) break entry;
315 };
316 const index = TrackedInst.Index.Unwrapped.wrap(.{
317 .tid = @enumFromInt(local_tid),
318 .index = @intCast(local_inst_index),
319 }, ip);
320 entry.hash = hash;
321 entry.release(index.toOptional());
322 }
323 }
324}
325
214326/// Analysis Unit. Represents a single entity which undergoes semantic analysis.
215327/// This is either a `Cau` or a runtime function.
216328/// The LSB is used as a tag bit.
......@@ -728,7 +840,7 @@ const Local = struct {
728840 else => @compileError("unsupported host"),
729841 };
730842 const Strings = List(struct { u8 });
731 const TrackedInsts = List(struct { TrackedInst });
843 const TrackedInsts = List(struct { TrackedInst.MaybeLost });
732844 const Maps = List(struct { FieldMap });
733845 const Caus = List(struct { Cau });
734846 const Navs = List(Nav.Repr);
......@@ -959,6 +1071,14 @@ const Local = struct {
9591071 mutable.list.release(new_list);
9601072 }
9611073
1074 pub fn viewAllowEmpty(mutable: Mutable) View {
1075 const capacity = mutable.list.header().capacity;
1076 return .{
1077 .bytes = mutable.list.bytes,
1078 .len = mutable.mutate.len,
1079 .capacity = capacity,
1080 };
1081 }
9621082 pub fn view(mutable: Mutable) View {
9631083 const capacity = mutable.list.header().capacity;
9641084 assert(capacity > 0); // optimizes `MultiArrayList.Slice.items`
......@@ -996,7 +1116,6 @@ const Local = struct {
9961116 fn header(list: ListSelf) *Header {
9971117 return @ptrFromInt(@intFromPtr(list.bytes) - bytes_offset);
9981118 }
999
10001119 pub fn view(list: ListSelf) View {
10011120 const capacity = list.header().capacity;
10021121 assert(capacity > 0); // optimizes `MultiArrayList.Slice.items`
......@@ -11000,7 +11119,6 @@ pub fn getOrPutTrailingString(
1100011119 shard.mutate.string_map.mutex.lock();
1100111120 defer shard.mutate.string_map.mutex.unlock();
1100211121 if (map.entries != shard.shared.string_map.entries) {
11003 shard.mutate.string_map.len += 1;
1100411122 map = shard.shared.string_map;
1100511123 map_mask = map.header().mask();
1100611124 map_index = hash;
src/Sema.zig+14-15
......@@ -999,7 +999,7 @@ fn analyzeBodyInner(
999999 // The hashmap lookup in here is a little expensive, and LLVM fails to optimize it away.
10001000 if (build_options.enable_logging) {
10011001 std.log.scoped(.sema_zir).debug("sema ZIR {s} %{d}", .{ sub_file_path: {
1002 const file_index = block.src_base_inst.resolveFull(&zcu.intern_pool).file;
1002 const file_index = block.src_base_inst.resolveFile(&zcu.intern_pool);
10031003 const file = zcu.fileByIndex(file_index);
10041004 break :sub_file_path file.sub_file_path;
10051005 }, inst });
......@@ -2873,7 +2873,7 @@ fn createTypeName(
28732873 .anon => {}, // handled after switch
28742874 .parent => return block.type_name_ctx,
28752875 .func => func_strat: {
2876 const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip));
2876 const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip) orelse return error.AnalysisFail);
28772877 const zir_tags = sema.code.instructions.items(.tag);
28782878
28792879 var buf: std.ArrayListUnmanaged(u8) = .{};
......@@ -5487,7 +5487,7 @@ fn failWithBadMemberAccess(
54875487 .Enum => "enum",
54885488 else => unreachable,
54895489 };
5490 if (agg_ty.typeDeclInst(zcu)) |inst| if (inst.resolve(ip) == .main_struct_inst) {
5490 if (agg_ty.typeDeclInst(zcu)) |inst| if ((inst.resolve(ip) orelse return error.AnalysisFail) == .main_struct_inst) {
54915491 return sema.fail(block, field_src, "root struct of file '{}' has no member named '{}'", .{
54925492 agg_ty.fmt(pt), field_name.fmt(ip),
54935493 });
......@@ -6041,8 +6041,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
60416041 return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
60426042
60436043 const path_digest = zcu.filePathDigest(result.file_index);
6044 const old_root_type = zcu.fileRootType(result.file_index);
6045 pt.astGenFile(result.file, path_digest, old_root_type) catch |err|
6044 pt.astGenFile(result.file, path_digest) catch |err|
60466045 return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
60476046
60486047 // TODO: register some kind of dependency on the file.
......@@ -7778,7 +7777,7 @@ fn analyzeCall(
77787777 // the AIR instructions of the callsite. The callee could be a generic function
77797778 // which means its parameter type expressions must be resolved in order and used
77807779 // to successively coerce the arguments.
7781 const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip));
7780 const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail);
77827781 try ics.callee().inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body);
77837782
77847783 var arg_i: u32 = 0;
......@@ -7823,7 +7822,7 @@ fn analyzeCall(
78237822 // each of the parameters, resolving the return type and providing it to the child
78247823 // `Sema` so that it can be used for the `ret_ptr` instruction.
78257824 const ret_ty_inst = if (fn_info.ret_ty_body.len != 0)
7826 try sema.resolveInlineBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip))
7825 try sema.resolveInlineBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail)
78277826 else
78287827 try sema.resolveInst(fn_info.ret_ty_ref);
78297828 const ret_ty_src: LazySrcLoc = .{ .base_node_inst = module_fn.zir_body_inst, .offset = .{ .node_offset_fn_type_ret_ty = 0 } };
......@@ -8210,7 +8209,7 @@ fn instantiateGenericCall(
82108209 const fn_nav = ip.getNav(generic_owner_func.owner_nav);
82118210 const fn_cau = ip.getCau(fn_nav.analysis_owner.unwrap().?);
82128211 const fn_zir = zcu.namespacePtr(fn_cau.namespace).fileScope(zcu).zir;
8213 const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip));
8212 const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip) orelse return error.AnalysisFail);
82148213
82158214 const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count());
82168215 @memset(comptime_args, .none);
......@@ -9416,7 +9415,7 @@ fn zirFunc(
94169415 break :cau generic_owner_nav.analysis_owner.unwrap().?;
94179416 } else sema.owner.unwrap().cau;
94189417 const fn_is_exported = exported: {
9419 const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip);
9418 const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip) orelse return error.AnalysisFail;
94209419 const zir_decl = sema.code.getDeclaration(decl_inst)[0];
94219420 break :exported zir_decl.flags.is_export;
94229421 };
......@@ -26125,7 +26124,7 @@ fn zirVarExtended(
2612526124 const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 });
2612626125
2612726126 const decl_inst, const decl_bodies = decl: {
26128 const decl_inst = sema.getOwnerCauDeclInst().resolve(ip);
26127 const decl_inst = sema.getOwnerCauDeclInst().resolve(ip) orelse return error.AnalysisFail;
2612926128 const zir_decl, const extra_end = sema.code.getDeclaration(decl_inst);
2613026129 break :decl .{ decl_inst, zir_decl.getBodies(extra_end, sema.code) };
2613126130 };
......@@ -26354,7 +26353,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
2635426353 break :decl_inst cau.zir_index;
2635526354 } else sema.getOwnerCauDeclInst(); // not an instantiation so we're analyzing a function declaration Cau
2635626355
26357 const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&mod.intern_pool))[0];
26356 const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&mod.intern_pool) orelse return error.AnalysisFail)[0];
2635826357 if (zir_decl.flags.is_export) {
2635926358 break :cc .C;
2636026359 }
......@@ -35505,7 +35504,7 @@ fn semaBackingIntType(pt: Zcu.PerThread, struct_type: InternPool.LoadedStructTyp
3550535504 break :blk accumulator;
3550635505 };
3550735506
35508 const zir_index = struct_type.zir_index.unwrap().?.resolve(ip);
35507 const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail;
3550935508 const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended;
3551035509 assert(extended.opcode == .struct_decl);
3551135510 const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
......@@ -36120,7 +36119,7 @@ fn semaStructFields(
3612036119 const cau_index = struct_type.cau.unwrap().?;
3612136120 const namespace_index = ip.getCau(cau_index).namespace;
3612236121 const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir;
36123 const zir_index = struct_type.zir_index.unwrap().?.resolve(ip);
36122 const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail;
3612436123
3612536124 const fields_len, const small, var extra_index = structZirInfo(zir, zir_index);
3612636125
......@@ -36343,7 +36342,7 @@ fn semaStructFieldInits(
3634336342 const cau_index = struct_type.cau.unwrap().?;
3634436343 const namespace_index = ip.getCau(cau_index).namespace;
3634536344 const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir;
36346 const zir_index = struct_type.zir_index.unwrap().?.resolve(ip);
36345 const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail;
3634736346 const fields_len, const small, var extra_index = structZirInfo(zir, zir_index);
3634836347
3634936348 var comptime_err_ret_trace = std.ArrayList(LazySrcLoc).init(gpa);
......@@ -36477,7 +36476,7 @@ fn semaUnionFields(pt: Zcu.PerThread, arena: Allocator, union_ty: InternPool.Ind
3647736476 const ip = &zcu.intern_pool;
3647836477 const cau_index = union_type.cau;
3647936478 const zir = zcu.namespacePtr(union_type.namespace).fileScope(zcu).zir;
36480 const zir_index = union_type.zir_index.resolve(ip);
36479 const zir_index = union_type.zir_index.resolve(ip) orelse return error.AnalysisFail;
3648136480 const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended;
3648236481 assert(extended.opcode == .union_decl);
3648336482 const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small);
src/Type.zig+1-1
......@@ -3437,7 +3437,7 @@ pub fn typeDeclSrcLine(ty: Type, zcu: *Zcu) ?u32 {
34373437 },
34383438 else => return null,
34393439 };
3440 const info = tracked.resolveFull(&zcu.intern_pool);
3440 const info = tracked.resolveFull(&zcu.intern_pool) orelse return null;
34413441 const file = zcu.fileByIndex(info.file);
34423442 assert(file.zir_loaded);
34433443 const zir = file.zir;
src/Zcu.zig+40-79
......@@ -162,12 +162,6 @@ outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{},
162162/// Such `AnalUnit`s are ready for immediate re-analysis.
163163/// See `findOutdatedToAnalyze` for details.
164164outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .{},
165/// This contains a set of struct types whose corresponding `Cau` may not be in
166/// `outdated`, but are the root types of files which have updated source and
167/// thus must be re-analyzed. If such a type is only in this set, the struct type
168/// index may be preserved (only the namespace might change). If its owned `Cau`
169/// is also outdated, the struct type index must be recreated.
170outdated_file_root: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{},
171165/// This contains a list of AnalUnit whose analysis or codegen failed, but the
172166/// failure was something like running out of disk space, and trying again may
173167/// succeed. On the next update, we will flush this list, marking all members of
......@@ -2025,7 +2019,7 @@ pub const LazySrcLoc = struct {
20252019 pub fn resolveBaseNode(base_node_inst: InternPool.TrackedInst.Index, zcu: *Zcu) struct { *File, Ast.Node.Index } {
20262020 const ip = &zcu.intern_pool;
20272021 const file_index, const zir_inst = inst: {
2028 const info = base_node_inst.resolveFull(ip);
2022 const info = base_node_inst.resolveFull(ip) orelse @panic("TODO: resolve source location relative to lost inst");
20292023 break :inst .{ info.file, info.inst };
20302024 };
20312025 const file = zcu.fileByIndex(file_index);
......@@ -2148,7 +2142,6 @@ pub fn deinit(zcu: *Zcu) void {
21482142 zcu.potentially_outdated.deinit(gpa);
21492143 zcu.outdated.deinit(gpa);
21502144 zcu.outdated_ready.deinit(gpa);
2151 zcu.outdated_file_root.deinit(gpa);
21522145 zcu.retryable_failures.deinit(gpa);
21532146
21542147 zcu.test_functions.deinit(gpa);
......@@ -2355,8 +2348,6 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
23552348pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
23562349 if (!zcu.comp.incremental) return null;
23572350
2358 if (true) @panic("TODO: findOutdatedToAnalyze");
2359
23602351 if (zcu.outdated.count() == 0 and zcu.potentially_outdated.count() == 0) {
23612352 log.debug("findOutdatedToAnalyze: no outdated depender", .{});
23622353 return null;
......@@ -2381,87 +2372,57 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
23812372 return zcu.outdated_ready.keys()[0];
23822373 }
23832374
2384 // Next, we will see if there is any outdated file root which was not in
2385 // `outdated`. This set will be small (number of files changed in this
2386 // update), so it's alright for us to just iterate here.
2387 for (zcu.outdated_file_root.keys()) |file_decl| {
2388 const decl_depender = AnalUnit.wrap(.{ .decl = file_decl });
2389 if (zcu.outdated.contains(decl_depender)) {
2390 // Since we didn't hit this in the first loop, this Decl must have
2391 // pending dependencies, so is ineligible.
2392 continue;
2393 }
2394 if (zcu.potentially_outdated.contains(decl_depender)) {
2395 // This Decl's struct may or may not need to be recreated depending
2396 // on whether it is outdated. If we analyzed it now, we would have
2397 // to assume it was outdated and recreate it!
2398 continue;
2399 }
2400 log.debug("findOutdatedToAnalyze: outdated file root decl '{d}'", .{file_decl});
2401 return decl_depender;
2402 }
2403
2404 // There is no single AnalUnit which is ready for re-analysis. Instead, we
2405 // must assume that some Decl with PO dependencies is outdated - e.g. in the
2406 // above example we arbitrarily pick one of A or B. We should select a Decl,
2407 // since a Decl is definitely responsible for the loop in the dependency
2408 // graph (since you can't depend on a runtime function analysis!).
2375 // There is no single AnalUnit which is ready for re-analysis. Instead, we must assume that some
2376 // Cau with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of
2377 // A or B. We should select a Cau, since a Cau is definitely responsible for the loop in the
2378 // dependency graph (since IES dependencies can't have loops). We should also, of course, not
2379 // select a Cau owned by a `comptime` declaration, since you can't depend on those!
24092380
2410 // The choice of this Decl could have a big impact on how much total
2411 // analysis we perform, since if analysis concludes its tyval is unchanged,
2412 // then other PO AnalUnit may be resolved as up-to-date. To hopefully avoid
2413 // doing too much work, let's find a Decl which the most things depend on -
2414 // the idea is that this will resolve a lot of loops (but this is only a
2415 // heuristic).
2381 // The choice of this Cau could have a big impact on how much total analysis we perform, since
2382 // if analysis concludes any dependencies on its result are up-to-date, then other PO AnalUnit
2383 // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a Decl
2384 // which the most things depend on - the idea is that this will resolve a lot of loops (but this
2385 // is only a heuristic).
24162386
24172387 log.debug("findOutdatedToAnalyze: no trivial ready, using heuristic; {d} outdated, {d} PO", .{
24182388 zcu.outdated.count(),
24192389 zcu.potentially_outdated.count(),
24202390 });
24212391
2422 const Decl = {};
2423
2424 var chosen_decl_idx: ?Decl.Index = null;
2425 var chosen_decl_dependers: u32 = undefined;
2426
2427 for (zcu.outdated.keys()) |depender| {
2428 const decl_index = switch (depender.unwrap()) {
2429 .decl => |d| d,
2430 .func => continue,
2431 };
2432
2433 var n: u32 = 0;
2434 var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index });
2435 while (it.next()) |_| n += 1;
2392 const ip = &zcu.intern_pool;
24362393
2437 if (chosen_decl_idx == null or n > chosen_decl_dependers) {
2438 chosen_decl_idx = decl_index;
2439 chosen_decl_dependers = n;
2440 }
2441 }
2394 var chosen_cau: ?InternPool.Cau.Index = null;
2395 var chosen_cau_dependers: u32 = undefined;
24422396
2443 for (zcu.potentially_outdated.keys()) |depender| {
2444 const decl_index = switch (depender.unwrap()) {
2445 .decl => |d| d,
2446 .func => continue,
2447 };
2397 inline for (.{ zcu.outdated.keys(), zcu.potentially_outdated.keys() }) |outdated_units| {
2398 for (outdated_units) |unit| {
2399 const cau = switch (unit.unwrap()) {
2400 .cau => |cau| cau,
2401 .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice
2402 };
2403 const cau_owner = ip.getCau(cau).owner;
24482404
2449 var n: u32 = 0;
2450 var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index });
2451 while (it.next()) |_| n += 1;
2405 var n: u32 = 0;
2406 var it = ip.dependencyIterator(switch (cau_owner.unwrap()) {
2407 .none => continue, // there can be no dependencies on this `Cau` so it is a terrible choice
2408 .type => |ty| .{ .interned = ty },
2409 .nav => |nav| .{ .nav_val = nav },
2410 });
2411 while (it.next()) |_| n += 1;
24522412
2453 if (chosen_decl_idx == null or n > chosen_decl_dependers) {
2454 chosen_decl_idx = decl_index;
2455 chosen_decl_dependers = n;
2413 if (chosen_cau == null or n > chosen_cau_dependers) {
2414 chosen_cau = cau;
2415 chosen_cau_dependers = n;
2416 }
24562417 }
24572418 }
24582419
2459 log.debug("findOutdatedToAnalyze: heuristic returned Decl {d} ({d} dependers)", .{
2460 chosen_decl_idx.?,
2461 chosen_decl_dependers,
2420 log.debug("findOutdatedToAnalyze: heuristic returned Cau {d} ({d} dependers)", .{
2421 @intFromEnum(chosen_cau.?),
2422 chosen_cau_dependers,
24622423 });
24632424
2464 return AnalUnit.wrap(.{ .decl = chosen_decl_idx.? });
2425 return AnalUnit.wrap(.{ .cau = chosen_cau.? });
24652426}
24662427
24672428/// During an incremental update, before semantic analysis, call this to flush all values from
......@@ -2583,7 +2544,7 @@ pub fn mapOldZirToNew(
25832544 break :inst unnamed_tests.items[unnamed_test_idx];
25842545 },
25852546 _ => inst: {
2586 const name_nts = new_decl.name.toString(old_zir).?;
2547 const name_nts = new_decl.name.toString(new_zir).?;
25872548 const name = new_zir.nullTerminatedString(name_nts);
25882549 if (new_decl.name.isNamedTest(new_zir)) {
25892550 break :inst named_tests.get(name) orelse continue;
......@@ -3093,7 +3054,7 @@ pub fn navSrcLoc(zcu: *const Zcu, nav_index: InternPool.Nav.Index) LazySrcLoc {
30933054
30943055pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 {
30953056 const ip = &zcu.intern_pool;
3096 const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip);
3057 const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip).?;
30973058 const zir = zcu.fileByIndex(inst_info.file).zir;
30983059 const inst = zir.instructions.get(@intFromEnum(inst_info.inst));
30993060 assert(inst.tag == .declaration);
......@@ -3106,7 +3067,7 @@ pub fn navValue(zcu: *const Zcu, nav_index: InternPool.Nav.Index) Value {
31063067
31073068pub fn navFileScopeIndex(zcu: *Zcu, nav: InternPool.Nav.Index) File.Index {
31083069 const ip = &zcu.intern_pool;
3109 return ip.getNav(nav).srcInst(ip).resolveFull(ip).file;
3070 return ip.getNav(nav).srcInst(ip).resolveFile(ip);
31103071}
31113072
31123073pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File {
......@@ -3115,6 +3076,6 @@ pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File {
31153076
31163077pub fn cauFileScope(zcu: *Zcu, cau: InternPool.Cau.Index) *File {
31173078 const ip = &zcu.intern_pool;
3118 const file_index = ip.getCau(cau).zir_index.resolveFull(ip).file;
3079 const file_index = ip.getCau(cau).zir_index.resolveFile(ip);
31193080 return zcu.fileByIndex(file_index);
31203081}
src/Zcu/PerThread.zig+191-172
......@@ -39,7 +39,6 @@ pub fn astGenFile(
3939 pt: Zcu.PerThread,
4040 file: *Zcu.File,
4141 path_digest: Cache.BinDigest,
42 old_root_type: InternPool.Index,
4342) !void {
4443 dev.check(.ast_gen);
4544 assert(!file.mod.isBuiltin());
......@@ -299,25 +298,15 @@ pub fn astGenFile(
299298 file.status = .astgen_failure;
300299 return error.AnalysisFail;
301300 }
302
303 if (old_root_type != .none) {
304 // The root of this file must be re-analyzed, since the file has changed.
305 comp.mutex.lock();
306 defer comp.mutex.unlock();
307
308 log.debug("outdated file root type: {}", .{old_root_type});
309 try zcu.outdated_file_root.put(gpa, old_root_type, {});
310 }
311301}
312302
313303const UpdatedFile = struct {
314 file_index: Zcu.File.Index,
315304 file: *Zcu.File,
316305 inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index),
317306};
318307
319fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.ArrayListUnmanaged(UpdatedFile)) void {
320 for (updated_files.items) |*elem| elem.inst_map.deinit(gpa);
308fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.AutoArrayHashMapUnmanaged(Zcu.File.Index, UpdatedFile)) void {
309 for (updated_files.values()) |*elem| elem.inst_map.deinit(gpa);
321310 updated_files.deinit(gpa);
322311}
323312
......@@ -328,143 +317,166 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
328317 const gpa = zcu.gpa;
329318
330319 // We need to visit every updated File for every TrackedInst in InternPool.
331 var updated_files: std.ArrayListUnmanaged(UpdatedFile) = .{};
320 var updated_files: std.AutoArrayHashMapUnmanaged(Zcu.File.Index, UpdatedFile) = .{};
332321 defer cleanupUpdatedFiles(gpa, &updated_files);
333322 for (zcu.import_table.values()) |file_index| {
334323 const file = zcu.fileByIndex(file_index);
335324 const old_zir = file.prev_zir orelse continue;
336325 const new_zir = file.zir;
337 try updated_files.append(gpa, .{
338 .file_index = file_index,
326 const gop = try updated_files.getOrPut(gpa, file_index);
327 assert(!gop.found_existing);
328 gop.value_ptr.* = .{
339329 .file = file,
340330 .inst_map = .{},
341 });
342 const inst_map = &updated_files.items[updated_files.items.len - 1].inst_map;
343 try Zcu.mapOldZirToNew(gpa, old_zir.*, new_zir, inst_map);
331 };
332 if (!new_zir.hasCompileErrors()) {
333 try Zcu.mapOldZirToNew(gpa, old_zir.*, file.zir, &gop.value_ptr.inst_map);
334 }
344335 }
345336
346 if (updated_files.items.len == 0)
337 if (updated_files.count() == 0)
347338 return;
348339
349340 for (ip.locals, 0..) |*local, tid| {
350341 const tracked_insts_list = local.getMutableTrackedInsts(gpa);
351 for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| {
352 for (updated_files.items) |updated_file| {
353 const file_index = updated_file.file_index;
354 if (tracked_inst.file != file_index) continue;
355
356 const file = updated_file.file;
357 const old_zir = file.prev_zir.?.*;
358 const new_zir = file.zir;
359 const old_tag = old_zir.instructions.items(.tag);
360 const old_data = old_zir.instructions.items(.data);
361 const inst_map = &updated_file.inst_map;
362
363 const old_inst = tracked_inst.inst;
364 const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{
365 .tid = @enumFromInt(tid),
366 .index = @intCast(tracked_inst_unwrapped_index),
367 }).wrap(ip);
368 tracked_inst.inst = inst_map.get(old_inst) orelse {
369 // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
370 log.debug("tracking failed for %{d}", .{old_inst});
371 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
372 continue;
373 };
342 for (tracked_insts_list.viewAllowEmpty().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| {
343 const file_index = tracked_inst.file;
344 const updated_file = updated_files.get(file_index) orelse continue;
374345
375 if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: {
376 if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| {
377 if (std.zig.srcHashEql(old_hash, new_hash)) {
378 break :hash_changed;
379 }
380 log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{
381 old_inst,
382 tracked_inst.inst,
383 std.fmt.fmtSliceHexLower(&old_hash),
384 std.fmt.fmtSliceHexLower(&new_hash),
385 });
346 const file = updated_file.file;
347
348 if (file.zir.hasCompileErrors()) {
349 // If we mark this as outdated now, users of this inst will just get a transitive analysis failure.
350 // Ultimately, they would end up throwing out potentially useful analysis results.
351 // So, do nothing. We already have the file failure -- that's sufficient for now!
352 continue;
353 }
354 const old_inst = tracked_inst.inst.unwrap() orelse continue; // we can't continue tracking lost insts
355 const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{
356 .tid = @enumFromInt(tid),
357 .index = @intCast(tracked_inst_unwrapped_index),
358 }).wrap(ip);
359 const new_inst = updated_file.inst_map.get(old_inst) orelse {
360 // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
361 log.debug("tracking failed for %{d}", .{old_inst});
362 tracked_inst.inst = .lost;
363 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
364 continue;
365 };
366 tracked_inst.inst = InternPool.TrackedInst.MaybeLost.ZirIndex.wrap(new_inst);
367
368 const old_zir = file.prev_zir.?.*;
369 const new_zir = file.zir;
370 const old_tag = old_zir.instructions.items(.tag);
371 const old_data = old_zir.instructions.items(.data);
372
373 if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: {
374 if (new_zir.getAssociatedSrcHash(new_inst)) |new_hash| {
375 if (std.zig.srcHashEql(old_hash, new_hash)) {
376 break :hash_changed;
386377 }
387 // The source hash associated with this instruction changed - invalidate relevant dependencies.
388 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
378 log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{
379 old_inst,
380 new_inst,
381 std.fmt.fmtSliceHexLower(&old_hash),
382 std.fmt.fmtSliceHexLower(&new_hash),
383 });
389384 }
385 // The source hash associated with this instruction changed - invalidate relevant dependencies.
386 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
387 }
390388
391 // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies.
392 const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {
393 .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {
394 .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,
395 else => false,
396 },
389 // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies.
390 const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {
391 .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {
392 .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,
397393 else => false,
398 };
399 if (!has_namespace) continue;
400
401 var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
402 defer old_names.deinit(zcu.gpa);
403 {
404 var it = old_zir.declIterator(old_inst);
405 while (it.next()) |decl_inst| {
406 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
407 switch (decl_name) {
408 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
409 _ => if (decl_name.isNamedTest(old_zir)) continue,
410 }
411 const name_zir = decl_name.toString(old_zir).?;
412 const name_ip = try zcu.intern_pool.getOrPutString(
413 zcu.gpa,
414 pt.tid,
415 old_zir.nullTerminatedString(name_zir),
416 .no_embedded_nulls,
417 );
418 try old_names.put(zcu.gpa, name_ip, {});
394 },
395 else => false,
396 };
397 if (!has_namespace) continue;
398
399 var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
400 defer old_names.deinit(zcu.gpa);
401 {
402 var it = old_zir.declIterator(old_inst);
403 while (it.next()) |decl_inst| {
404 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
405 switch (decl_name) {
406 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
407 _ => if (decl_name.isNamedTest(old_zir)) continue,
419408 }
409 const name_zir = decl_name.toString(old_zir).?;
410 const name_ip = try zcu.intern_pool.getOrPutString(
411 zcu.gpa,
412 pt.tid,
413 old_zir.nullTerminatedString(name_zir),
414 .no_embedded_nulls,
415 );
416 try old_names.put(zcu.gpa, name_ip, {});
420417 }
421 var any_change = false;
422 {
423 var it = new_zir.declIterator(tracked_inst.inst);
424 while (it.next()) |decl_inst| {
425 const decl_name = new_zir.getDeclaration(decl_inst)[0].name;
426 switch (decl_name) {
427 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
428 _ => if (decl_name.isNamedTest(new_zir)) continue,
429 }
430 const name_zir = decl_name.toString(new_zir).?;
431 const name_ip = try zcu.intern_pool.getOrPutString(
432 zcu.gpa,
433 pt.tid,
434 new_zir.nullTerminatedString(name_zir),
435 .no_embedded_nulls,
436 );
437 if (!old_names.swapRemove(name_ip)) continue;
438 // Name added
439 any_change = true;
440 try zcu.markDependeeOutdated(.{ .namespace_name = .{
441 .namespace = tracked_inst_index,
442 .name = name_ip,
443 } });
418 }
419 var any_change = false;
420 {
421 var it = new_zir.declIterator(new_inst);
422 while (it.next()) |decl_inst| {
423 const decl_name = new_zir.getDeclaration(decl_inst)[0].name;
424 switch (decl_name) {
425 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
426 _ => if (decl_name.isNamedTest(new_zir)) continue,
444427 }
445 }
446 // The only elements remaining in `old_names` now are any names which were removed.
447 for (old_names.keys()) |name_ip| {
428 const name_zir = decl_name.toString(new_zir).?;
429 const name_ip = try zcu.intern_pool.getOrPutString(
430 zcu.gpa,
431 pt.tid,
432 new_zir.nullTerminatedString(name_zir),
433 .no_embedded_nulls,
434 );
435 if (!old_names.swapRemove(name_ip)) continue;
436 // Name added
448437 any_change = true;
449438 try zcu.markDependeeOutdated(.{ .namespace_name = .{
450439 .namespace = tracked_inst_index,
451440 .name = name_ip,
452441 } });
453442 }
443 }
444 // The only elements remaining in `old_names` now are any names which were removed.
445 for (old_names.keys()) |name_ip| {
446 any_change = true;
447 try zcu.markDependeeOutdated(.{ .namespace_name = .{
448 .namespace = tracked_inst_index,
449 .name = name_ip,
450 } });
451 }
454452
455 if (any_change) {
456 try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index });
457 }
453 if (any_change) {
454 try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index });
458455 }
459456 }
460457 }
461458
462 for (updated_files.items) |updated_file| {
459 try ip.rehashTrackedInsts(gpa, pt.tid);
460
461 for (updated_files.keys(), updated_files.values()) |file_index, updated_file| {
463462 const file = updated_file.file;
464 const prev_zir = file.prev_zir.?;
465 file.prev_zir = null;
466 prev_zir.deinit(gpa);
467 gpa.destroy(prev_zir);
463 if (file.zir.hasCompileErrors()) {
464 // Keep `prev_zir` around: it's the last non-error ZIR.
465 // Don't update the namespace, as we have no new data to update *to*.
466 } else {
467 const prev_zir = file.prev_zir.?;
468 file.prev_zir = null;
469 prev_zir.deinit(gpa);
470 gpa.destroy(prev_zir);
471
472 // For every file which has changed, re-scan the namespace of the file's root struct type.
473 // These types are special-cased because they don't have an enclosing declaration which will
474 // be re-analyzed (causing the struct's namespace to be re-scanned). It's fine to do this
475 // now because this work is fast (no actual Sema work is happening, we're just updating the
476 // namespace contents). We must do this after updating ZIR refs above, since `scanNamespace`
477 // will track some instructions.
478 try pt.updateFileNamespace(file_index);
479 }
468480 }
469481}
470482
......@@ -473,6 +485,8 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
473485pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
474486 const file_root_type = pt.zcu.fileRootType(file_index);
475487 if (file_root_type != .none) {
488 // The namespace is already up-to-date thanks to the `updateFileNamespace` calls at the
489 // start of this update. We just have to check whether the type itself is okay!
476490 const file_root_type_cau = pt.zcu.intern_pool.loadStructType(file_root_type).cau.unwrap().?;
477491 return pt.ensureCauAnalyzed(file_root_type_cau);
478492 } else {
......@@ -493,7 +507,6 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
493507
494508 const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index });
495509 const cau = ip.getCau(cau_index);
496 const inst_info = cau.zir_index.resolveFull(ip);
497510
498511 log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)});
499512
......@@ -516,12 +529,9 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
516529 _ = zcu.outdated_ready.swapRemove(anal_unit);
517530 }
518531
519 // TODO: this only works if namespace lookups in Sema trigger `ensureCauAnalyzed`, because
520 // `outdated_file_root` information is not "viral", so we need that a namespace lookup first
521 // handles the case where the file root is not an outdated *type* but does have an outdated
522 // *namespace*. A more logically simple alternative may be for a file's root struct to register
523 // a dependency on the file's entire source code (hash). Alternatively, we could make sure that
524 // these are always handled first in an update. Actually, that's probably the best option.
532 const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
533
534 // TODO: document this elsewhere mlugg!
525535 // For my own benefit, here's how a namespace update for a normal (non-file-root) type works:
526536 // `const S = struct { ... };`
527537 // We are adding or removing a declaration within this `struct`.
......@@ -535,16 +545,12 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
535545 // * we basically do `scanDecls`, updating the namespace as needed
536546 // * TODO: optimize this to make sure we only do it once a generation i guess?
537547 // * so everyone lived happily ever after
538 const file_root_outdated = switch (cau.owner.unwrap()) {
539 .type => |ty| zcu.outdated_file_root.swapRemove(ty),
540 .nav, .none => false,
541 };
542548
543549 if (zcu.fileByIndex(inst_info.file).status != .success_zir) {
544550 return error.AnalysisFail;
545551 }
546552
547 if (!cau_outdated and !file_root_outdated) {
553 if (!cau_outdated) {
548554 // We can trust the current information about this `Cau`.
549555 if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) {
550556 return error.AnalysisFail;
......@@ -571,10 +577,13 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
571577
572578 const sema_result: SemaCauResult = res: {
573579 if (inst_info.inst == .main_struct_inst) {
574 const changed = try pt.semaFileUpdate(inst_info.file, cau_outdated);
580 // Note that this is definitely a *recreation* due to outdated, because
581 // this instruction indicates that `cau.owner` is a `type`, which only
582 // reaches here if `cau_outdated`.
583 try pt.recreateFileRoot(inst_info.file);
575584 break :res .{
576 .invalidate_decl_val = changed,
577 .invalidate_decl_ref = changed,
585 .invalidate_decl_val = true,
586 .invalidate_decl_ref = true,
578587 };
579588 }
580589
......@@ -690,8 +699,8 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
690699 zcu.potentially_outdated.swapRemove(anal_unit);
691700
692701 if (func_outdated) {
693 dev.check(.incremental);
694702 _ = zcu.outdated_ready.swapRemove(anal_unit);
703 dev.check(.incremental);
695704 zcu.deleteUnitExports(anal_unit);
696705 zcu.deleteUnitReferences(anal_unit);
697706 }
......@@ -920,12 +929,9 @@ fn createFileRootStruct(
920929 return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index);
921930}
922931
923/// Re-analyze the root type of a file on an incremental update.
924/// If `type_outdated`, the struct type itself is considered outdated and is
925/// reconstructed at a new InternPool index. Otherwise, the namespace is just
926/// re-analyzed. Returns whether the decl's tyval was invalidated.
927/// Returns `error.AnalysisFail` if the file has an error.
928fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated: bool) Zcu.SemaError!bool {
932/// Recreate the root type of a file after it becomes outdated. A new struct type
933/// is constructed at a new InternPool index, reusing the namespace for efficiency.
934fn recreateFileRoot(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
929935 const zcu = pt.zcu;
930936 const ip = &zcu.intern_pool;
931937 const file = zcu.fileByIndex(file_index);
......@@ -934,48 +940,58 @@ fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated:
934940
935941 assert(file_root_type != .none);
936942
937 log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{
943 log.debug("recreateFileRoot mod={s} sub_file_path={s}", .{
938944 file.mod.fully_qualified_name,
939945 file.sub_file_path,
940 type_outdated,
941946 });
942947
943948 if (file.status != .success_zir) {
944949 return error.AnalysisFail;
945950 }
946951
947 if (type_outdated) {
948 // Invalidate the existing type, reusing its namespace.
949 const file_root_type_cau = ip.loadStructType(file_root_type).cau.unwrap().?;
950 ip.removeDependenciesForDepender(
951 zcu.gpa,
952 InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }),
953 );
954 ip.remove(pt.tid, file_root_type);
955 _ = try pt.createFileRootStruct(file_index, namespace_index);
956 return true;
957 }
958
959 // Only the struct's namespace is outdated.
960 // Preserve the type - just scan the namespace again.
952 // Invalidate the existing type, reusing its namespace.
953 const file_root_type_cau = ip.loadStructType(file_root_type).cau.unwrap().?;
954 ip.removeDependenciesForDepender(
955 zcu.gpa,
956 InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }),
957 );
958 ip.remove(pt.tid, file_root_type);
959 _ = try pt.createFileRootStruct(file_index, namespace_index);
960}
961961
962 const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
963 const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
962/// Re-scan the namespace of a file's root struct type on an incremental update.
963/// The file must have successfully populated ZIR.
964/// If the file's root struct type is not populated (the file is unreferenced), nothing is done.
965/// This is called by `updateZirRefs` for all updated files before the main work loop.
966/// This function does not perform any semantic analysis.
967fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void {
968 const zcu = pt.zcu;
964969
965 var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len;
966 extra_index += @intFromBool(small.has_fields_len);
967 const decls_len = if (small.has_decls_len) blk: {
968 const decls_len = file.zir.extra[extra_index];
969 extra_index += 1;
970 break :blk decls_len;
971 } else 0;
972 const decls = file.zir.bodySlice(extra_index, decls_len);
970 const file = zcu.fileByIndex(file_index);
971 assert(file.status == .success_zir);
972 const file_root_type = zcu.fileRootType(file_index);
973 if (file_root_type == .none) return;
973974
974 if (!type_outdated) {
975 try pt.scanNamespace(namespace_index, decls);
976 }
975 log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{
976 file.mod.fully_qualified_name,
977 file.sub_file_path,
978 });
977979
978 return false;
980 const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu);
981 const decls = decls: {
982 const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
983 const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
984
985 var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len;
986 extra_index += @intFromBool(small.has_fields_len);
987 const decls_len = if (small.has_decls_len) blk: {
988 const decls_len = file.zir.extra[extra_index];
989 extra_index += 1;
990 break :blk decls_len;
991 } else 0;
992 break :decls file.zir.bodySlice(extra_index, decls_len);
993 };
994 try pt.scanNamespace(namespace_index, decls);
979995}
980996
981997/// Regardless of the file status, will create a `Decl` if none exists so that we can track
......@@ -1052,7 +1068,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
10521068 const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index });
10531069
10541070 const cau = ip.getCau(cau_index);
1055 const inst_info = cau.zir_index.resolveFull(ip);
1071 const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
10561072 const file = zcu.fileByIndex(inst_info.file);
10571073 const zir = file.zir;
10581074
......@@ -1944,6 +1960,9 @@ const ScanDeclIter = struct {
19441960 const cau, const nav = if (existing_cau) |cau_index| cau_nav: {
19451961 const nav_index = ip.getCau(cau_index).owner.unwrap().nav;
19461962 const nav = ip.getNav(nav_index);
1963 if (nav.name != name) {
1964 std.debug.panic("'{}' vs '{}'", .{ nav.name.fmt(ip), name.fmt(ip) });
1965 }
19471966 assert(nav.name == name);
19481967 assert(nav.fqn == fqn);
19491968 break :cau_nav .{ cau_index, nav_index };
......@@ -2011,7 +2030,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
20112030
20122031 const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index });
20132032 const func = zcu.funcInfo(func_index);
2014 const inst_info = func.zir_body_inst.resolveFull(ip);
2033 const inst_info = func.zir_body_inst.resolveFull(ip) orelse return error.AnalysisFail;
20152034 const file = zcu.fileByIndex(inst_info.file);
20162035 const zir = file.zir;
20172036
......@@ -2097,7 +2116,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
20972116 };
20982117 defer inner_block.instructions.deinit(gpa);
20992118
2100 const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip));
2119 const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip) orelse return error.AnalysisFail);
21012120
21022121 // Here we are performing "runtime semantic analysis" for a function body, which means
21032122 // we must map the parameter ZIR instructions to `arg` AIR instructions.
src/codegen.zig+1-1
......@@ -98,7 +98,7 @@ pub fn generateLazyFunction(
9898 debug_output: DebugInfoOutput,
9999) CodeGenError!Result {
100100 const zcu = pt.zcu;
101 const file = Type.fromInterned(lazy_sym.ty).typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(&zcu.intern_pool).file;
101 const file = Type.fromInterned(lazy_sym.ty).typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(&zcu.intern_pool);
102102 const target = zcu.fileByIndex(file).mod.resolved_target.result;
103103 switch (target_util.zigBackend(target, false)) {
104104 else => unreachable,
src/codegen/c.zig+1-1
......@@ -2585,7 +2585,7 @@ pub fn genTypeDecl(
25852585 const ty = Type.fromInterned(index);
25862586 _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{});
25872587 try writer.writeByte(';');
2588 const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file;
2588 const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip);
25892589 if (!zcu.fileByIndex(file_scope).mod.strip) try writer.print(" /* {} */", .{
25902590 ty.containerTypeName(ip).fmt(ip),
25912591 });
src/codegen/llvm.zig+3-3
......@@ -1959,7 +1959,7 @@ pub const Object = struct {
19591959 );
19601960 }
19611961
1962 const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file);
1962 const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip));
19631963 const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace|
19641964 try o.namespaceToDebugScope(parent_namespace)
19651965 else
......@@ -2137,7 +2137,7 @@ pub const Object = struct {
21372137 const name = try o.allocTypeName(ty);
21382138 defer gpa.free(name);
21392139
2140 const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file);
2140 const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip));
21412141 const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace|
21422142 try o.namespaceToDebugScope(parent_namespace)
21432143 else
......@@ -2772,7 +2772,7 @@ pub const Object = struct {
27722772 fn makeEmptyNamespaceDebugType(o: *Object, ty: Type) !Builder.Metadata {
27732773 const zcu = o.pt.zcu;
27742774 const ip = &zcu.intern_pool;
2775 const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file);
2775 const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip));
27762776 const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace|
27772777 try o.namespaceToDebugScope(parent_namespace)
27782778 else