authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-05-25 18:02:16+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-05-25 18:02:16+01:00
logef35c3d5fefb8c14e17f3c7036bb21e808ee59be
treeb2178084647ef4ac98bf4d45273b4708ff0b7607
parentdc6ffc28b57a96fd03f62bc665b6ed28b8e9e67b
parent3d8e760552bc60d2c7f1f4df9c8a05c8aae2b769
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #23986 from mlugg/incremental-stuff

incremental: bugfix (and a debugging feature that helped me do that bugfix)

11 files changed, 651 insertions(+), 60 deletions(-)

lib/compiler/build_runner.zig+2
......@@ -236,6 +236,8 @@ pub fn main() !void {
236236 graph.debug_compiler_runtime_libs = true;
237237 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
238238 builder.debug_compile_errors = true;
239 } else if (mem.eql(u8, arg, "--debug-incremental")) {
240 builder.debug_incremental = true;
239241 } else if (mem.eql(u8, arg, "--system")) {
240242 // The usage text shows another argument after this parameter
241243 // but it is handled by the parent process. The build runner
lib/std/Build.zig+2
......@@ -59,6 +59,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
5959args: ?[]const []const u8 = null,
6060debug_log_scopes: []const []const u8 = &.{},
6161debug_compile_errors: bool = false,
62debug_incremental: bool = false,
6263debug_pkg_config: bool = false,
6364/// Number of stack frames captured when a `StackTrace` is recorded for debug purposes,
6465/// in particular at `Step` creation.
......@@ -385,6 +386,7 @@ fn createChildOnly(
385386 .cache_root = parent.cache_root,
386387 .debug_log_scopes = parent.debug_log_scopes,
387388 .debug_compile_errors = parent.debug_compile_errors,
389 .debug_incremental = parent.debug_incremental,
388390 .debug_pkg_config = parent.debug_pkg_config,
389391 .enable_darling = parent.enable_darling,
390392 .enable_qemu = parent.enable_qemu,
lib/std/Build/Step/Compile.zig+4
......@@ -1447,6 +1447,10 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
14471447 try zig_args.append("--debug-compile-errors");
14481448 }
14491449
1450 if (b.debug_incremental) {
1451 try zig_args.append("--debug-incremental");
1452 }
1453
14501454 if (b.verbose_cimport) try zig_args.append("--verbose-cimport");
14511455 if (b.verbose_air) try zig_args.append("--verbose-air");
14521456 if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path}));
src/Compilation.zig+12
......@@ -190,6 +190,8 @@ time_report: bool,
190190stack_report: bool,
191191debug_compiler_runtime_libs: bool,
192192debug_compile_errors: bool,
193/// Do not check this field directly. Instead, use the `debugIncremental` wrapper function.
194debug_incremental: bool,
193195incremental: bool,
194196alloc_failure_occurred: bool = false,
195197last_update_was_cache_hit: bool = false,
......@@ -768,6 +770,14 @@ pub const Directories = struct {
768770 }
769771};
770772
773/// This small wrapper function just checks whether debug extensions are enabled before checking
774/// `comp.debug_incremental`. It is inline so that comptime-known `false` propagates to the caller,
775/// preventing debugging features from making it into release builds of the compiler.
776pub inline fn debugIncremental(comp: *const Compilation) bool {
777 if (!build_options.enable_debug_extensions) return false;
778 return comp.debug_incremental;
779}
780
771781pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size;
772782pub const SemaError = Zcu.SemaError;
773783
......@@ -1598,6 +1608,7 @@ pub const CreateOptions = struct {
15981608 verbose_llvm_cpu_features: bool = false,
15991609 debug_compiler_runtime_libs: bool = false,
16001610 debug_compile_errors: bool = false,
1611 debug_incremental: bool = false,
16011612 incremental: bool = false,
16021613 /// Normally when you create a `Compilation`, Zig will automatically build
16031614 /// and link in required dependencies, such as compiler-rt and libc. When
......@@ -1968,6 +1979,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
19681979 .test_name_prefix = options.test_name_prefix,
19691980 .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs,
19701981 .debug_compile_errors = options.debug_compile_errors,
1982 .debug_incremental = options.debug_incremental,
19711983 .incremental = options.incremental,
19721984 .root_name = root_name,
19731985 .sysroot = sysroot,
src/IncrementalDebugServer.zig created+383
......@@ -0,0 +1,383 @@
1//! This is a simple TCP server which exposes a REPL useful for debugging incremental compilation
2//! issues. Eventually, this logic should move into `std.zig.Client`/`std.zig.Server` or something
3//! similar, but for now, this works. The server is enabled by the '--debug-incremental' CLI flag.
4//! The easiest way to interact with the REPL is to use `telnet`:
5//! ```
6//! telnet "::1" 7623
7//! ```
8//! 'help' will list available commands. When the debug server is enabled, the compiler tracks a lot
9//! of extra state (see `Zcu.IncrementalDebugState`), so note that RSS will be higher than usual.
10
11comptime {
12 // This file should only be referenced when debug extensions are enabled.
13 std.debug.assert(@import("build_options").enable_debug_extensions);
14}
15
16zcu: *Zcu,
17thread: ?std.Thread,
18running: std.atomic.Value(bool),
19/// Held by our owner when an update is in-progress, and held by us when responding to a command.
20/// So, essentially guards all access to `Compilation`, including `Zcu`.
21mutex: std.Thread.Mutex,
22
23pub fn init(zcu: *Zcu) IncrementalDebugServer {
24 return .{
25 .zcu = zcu,
26 .thread = null,
27 .running = .init(true),
28 .mutex = .{},
29 };
30}
31
32pub fn deinit(ids: *IncrementalDebugServer) void {
33 if (ids.thread) |t| {
34 ids.running.store(false, .monotonic);
35 t.join();
36 }
37}
38
39const port = 7623;
40pub fn spawn(ids: *IncrementalDebugServer) void {
41 std.debug.print("spawning incremental debug server on port {d}\n", .{port});
42 ids.thread = std.Thread.spawn(.{ .allocator = ids.zcu.comp.arena }, runThread, .{ids}) catch |err|
43 std.process.fatal("failed to spawn incremental debug server: {s}", .{@errorName(err)});
44}
45fn runThread(ids: *IncrementalDebugServer) void {
46 const gpa = ids.zcu.gpa;
47
48 var cmd_buf: [1024]u8 = undefined;
49 var text_out: std.ArrayListUnmanaged(u8) = .empty;
50 defer text_out.deinit(gpa);
51
52 const addr = std.net.Address.parseIp6("::", port) catch unreachable;
53 var server = addr.listen(.{}) catch @panic("IncrementalDebugServer: failed to listen");
54 defer server.deinit();
55 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");
56 defer conn.stream.close();
57
58 while (ids.running.load(.monotonic)) {
59 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
60 var fbs = std.io.fixedBufferStream(&cmd_buf);
61 conn.stream.reader().streamUntilDelimiter(fbs.writer(), '\n', cmd_buf.len) catch |err| switch (err) {
62 error.EndOfStream => break,
63 else => @panic("IncrementalDebugServer: failed to read command"),
64 };
65 const cmd_and_arg = std.mem.trim(u8, fbs.getWritten(), " \t\r\n");
66 const cmd: []const u8, const arg: []const u8 = if (std.mem.indexOfScalar(u8, cmd_and_arg, ' ')) |i|
67 .{ cmd_and_arg[0..i], cmd_and_arg[i + 1 ..] }
68 else
69 .{ cmd_and_arg, "" };
70
71 text_out.clearRetainingCapacity();
72 {
73 if (!ids.mutex.tryLock()) {
74 conn.stream.writeAll("waiting for in-progress update to finish...\n") catch @panic("IncrementalDebugServer: failed to write");
75 ids.mutex.lock();
76 }
77 defer ids.mutex.unlock();
78 handleCommand(ids.zcu, &text_out, cmd, arg) catch @panic("IncrementalDebugServer: out of memory");
79 }
80 text_out.append(gpa, '\n') catch @panic("IncrementalDebugServer: out of memory");
81 conn.stream.writeAll(text_out.items) catch @panic("IncrementalDebugServer: failed to write");
82 }
83 std.debug.print("closing incremental debug server\n", .{});
84}
85
86const help_str: []const u8 =
87 \\[str] arguments are any string.
88 \\[id] arguments are a numeric ID/index, like an InternPool index.
89 \\[unit] arguments are strings like 'func 1234' where '1234' is the relevant index (in this case an InternPool index).
90 \\
91 \\MISC
92 \\ summary
93 \\ Dump some information about the whole ZCU.
94 \\ nav_info [id]
95 \\ Dump basic info about a NAV.
96 \\
97 \\SEARCHING
98 \\ find_type [str]
99 \\ Find types (including dead ones) whose names contain the given substring.
100 \\ Starting with '^' or ending with '$' anchors to the start/end of the name.
101 \\ find_nav [str]
102 \\ Find NAVs (including dead ones) whose names contain the given substring.
103 \\ Starting with '^' or ending with '$' anchors to the start/end of the name.
104 \\
105 \\UNITS
106 \\ unit_info [unit]
107 \\ Dump basic info about an analysis unit.
108 \\ unit_dependencies [unit]
109 \\ List all units which an analysis unit depends on.
110 \\ unit_trace [unit]
111 \\ Dump the current reference trace of an analysis unit.
112 \\
113 \\TYPES
114 \\ type_info [id]
115 \\ Dump basic info about a type.
116 \\ type_namespace [id]
117 \\ List all declarations in the namespace of a type.
118 \\
119;
120
121fn handleCommand(zcu: *Zcu, output: *std.ArrayListUnmanaged(u8), cmd_str: []const u8, arg_str: []const u8) Allocator.Error!void {
122 const ip = &zcu.intern_pool;
123 const gpa = zcu.gpa;
124 const w = output.writer(gpa);
125 if (std.mem.eql(u8, cmd_str, "help")) {
126 try w.writeAll(help_str);
127 } else if (std.mem.eql(u8, cmd_str, "summary")) {
128 try w.print(
129 \\last generation: {d}
130 \\total container types: {d}
131 \\total NAVs: {d}
132 \\total units: {d}
133 \\
134 , .{
135 zcu.generation - 1,
136 zcu.incremental_debug_state.types.count(),
137 zcu.incremental_debug_state.navs.count(),
138 zcu.incremental_debug_state.units.count(),
139 });
140 } else if (std.mem.eql(u8, cmd_str, "nav_info")) {
141 const nav_index: InternPool.Nav.Index = @enumFromInt(parseIndex(arg_str) orelse return w.writeAll("malformed nav index"));
142 const create_gen = zcu.incremental_debug_state.navs.get(nav_index) orelse return w.writeAll("unknown nav index");
143 const nav = ip.getNav(nav_index);
144 try w.print(
145 \\name: '{}'
146 \\fqn: '{}'
147 \\status: {s}
148 \\created on generation: {d}
149 \\
150 , .{
151 nav.name.fmt(ip),
152 nav.fqn.fmt(ip),
153 @tagName(nav.status),
154 create_gen,
155 });
156 switch (nav.status) {
157 .unresolved => {},
158 .type_resolved, .fully_resolved => {
159 try w.writeAll("type: ");
160 try printType(.fromInterned(nav.typeOf(ip)), zcu, w);
161 try w.writeByte('\n');
162 },
163 }
164 } else if (std.mem.eql(u8, cmd_str, "find_type")) {
165 if (arg_str.len == 0) return w.writeAll("bad usage");
166 const anchor_start = arg_str[0] == '^';
167 const anchor_end = arg_str[arg_str.len - 1] == '$';
168 const query = arg_str[@intFromBool(anchor_start) .. arg_str.len - @intFromBool(anchor_end)];
169 var num_results: usize = 0;
170 for (zcu.incremental_debug_state.types.keys()) |type_ip_index| {
171 const ty: Type = .fromInterned(type_ip_index);
172 const ty_name = ty.containerTypeName(ip).toSlice(ip);
173 const success = switch (@as(u2, @intFromBool(anchor_start)) << 1 | @intFromBool(anchor_end)) {
174 0b00 => std.mem.indexOf(u8, ty_name, query) != null,
175 0b01 => std.mem.endsWith(u8, ty_name, query),
176 0b10 => std.mem.startsWith(u8, ty_name, query),
177 0b11 => std.mem.eql(u8, ty_name, query),
178 };
179 if (success) {
180 num_results += 1;
181 try w.print("* type {d} ('{s}')\n", .{ @intFromEnum(type_ip_index), ty_name });
182 }
183 }
184 try w.print("Found {d} results\n", .{num_results});
185 } else if (std.mem.eql(u8, cmd_str, "find_nav")) {
186 if (arg_str.len == 0) return w.writeAll("bad usage");
187 const anchor_start = arg_str[0] == '^';
188 const anchor_end = arg_str[arg_str.len - 1] == '$';
189 const query = arg_str[@intFromBool(anchor_start) .. arg_str.len - @intFromBool(anchor_end)];
190 var num_results: usize = 0;
191 for (zcu.incremental_debug_state.navs.keys()) |nav_index| {
192 const nav = ip.getNav(nav_index);
193 const nav_fqn = nav.fqn.toSlice(ip);
194 const success = switch (@as(u2, @intFromBool(anchor_start)) << 1 | @intFromBool(anchor_end)) {
195 0b00 => std.mem.indexOf(u8, nav_fqn, query) != null,
196 0b01 => std.mem.endsWith(u8, nav_fqn, query),
197 0b10 => std.mem.startsWith(u8, nav_fqn, query),
198 0b11 => std.mem.eql(u8, nav_fqn, query),
199 };
200 if (success) {
201 num_results += 1;
202 try w.print("* nav {d} ('{s}')\n", .{ @intFromEnum(nav_index), nav_fqn });
203 }
204 }
205 try w.print("Found {d} results\n", .{num_results});
206 } else if (std.mem.eql(u8, cmd_str, "unit_info")) {
207 const unit = parseAnalUnit(arg_str) orelse return w.writeAll("malformed anal unit");
208 const unit_info = zcu.incremental_debug_state.units.get(unit) orelse return w.writeAll("unknown anal unit");
209 var ref_str_buf: [32]u8 = undefined;
210 const ref_str: []const u8 = ref: {
211 const refs = try zcu.resolveReferences();
212 const ref = refs.get(unit) orelse break :ref "<unreferenced>";
213 const referencer = (ref orelse break :ref "<analysis root>").referencer;
214 break :ref printAnalUnit(referencer, &ref_str_buf);
215 };
216 const has_err: []const u8 = err: {
217 if (zcu.failed_analysis.contains(unit)) break :err "true";
218 if (zcu.transitive_failed_analysis.contains(unit)) break :err "true (transitive)";
219 break :err "false";
220 };
221 try w.print(
222 \\last update generation: {d}
223 \\current referencer: {s}
224 \\has error: {s}
225 \\
226 , .{
227 unit_info.last_update_gen,
228 ref_str,
229 has_err,
230 });
231 } else if (std.mem.eql(u8, cmd_str, "unit_dependencies")) {
232 const unit = parseAnalUnit(arg_str) orelse return w.writeAll("malformed anal unit");
233 const unit_info = zcu.incremental_debug_state.units.get(unit) orelse return w.writeAll("unknown anal unit");
234 for (unit_info.deps.items, 0..) |dependee, i| {
235 try w.print("[{d}] ", .{i});
236 switch (dependee) {
237 .src_hash, .namespace, .namespace_name, .zon_file, .embed_file => try w.print("{}", .{zcu.fmtDependee(dependee)}),
238 .nav_val, .nav_ty => |nav| try w.print("{s} {d}", .{ @tagName(dependee), @intFromEnum(nav) }),
239 .interned => |ip_index| switch (ip.indexToKey(ip_index)) {
240 .struct_type, .union_type, .enum_type => try w.print("type {d}", .{@intFromEnum(ip_index)}),
241 .func => try w.print("func {d}", .{@intFromEnum(ip_index)}),
242 else => unreachable,
243 },
244 .memoized_state => |stage| try w.print("memoized_state {s}", .{@tagName(stage)}),
245 }
246 try w.writeByte('\n');
247 }
248 } else if (std.mem.eql(u8, cmd_str, "unit_trace")) {
249 const unit = parseAnalUnit(arg_str) orelse return w.writeAll("malformed anal unit");
250 if (!zcu.incremental_debug_state.units.contains(unit)) return w.writeAll("unknown anal unit");
251 const refs = try zcu.resolveReferences();
252 if (!refs.contains(unit)) return w.writeAll("not referenced");
253 var opt_cur: ?AnalUnit = unit;
254 while (opt_cur) |cur| {
255 var buf: [32]u8 = undefined;
256 try w.print("* {s}\n", .{printAnalUnit(cur, &buf)});
257 opt_cur = if (refs.get(cur).?) |ref| ref.referencer else null;
258 }
259 } else if (std.mem.eql(u8, cmd_str, "type_info")) {
260 const ip_index: InternPool.Index = @enumFromInt(parseIndex(arg_str) orelse return w.writeAll("malformed ip index"));
261 const create_gen = zcu.incremental_debug_state.types.get(ip_index) orelse return w.writeAll("unknown type");
262 try w.print(
263 \\name: '{}'
264 \\created on generation: {d}
265 \\
266 , .{
267 Type.fromInterned(ip_index).containerTypeName(ip).fmt(ip),
268 create_gen,
269 });
270 } else if (std.mem.eql(u8, cmd_str, "type_namespace")) {
271 const ip_index: InternPool.Index = @enumFromInt(parseIndex(arg_str) orelse return w.writeAll("malformed ip index"));
272 if (!zcu.incremental_debug_state.types.contains(ip_index)) return w.writeAll("unknown type");
273 const ns = zcu.namespacePtr(Type.fromInterned(ip_index).getNamespaceIndex(zcu));
274 try w.print("{d} pub decls:\n", .{ns.pub_decls.count()});
275 for (ns.pub_decls.keys()) |nav| {
276 try w.print("* nav {d}\n", .{@intFromEnum(nav)});
277 }
278 try w.print("{d} non-pub decls:\n", .{ns.priv_decls.count()});
279 for (ns.priv_decls.keys()) |nav| {
280 try w.print("* nav {d}\n", .{@intFromEnum(nav)});
281 }
282 try w.print("{d} comptime decls:\n", .{ns.comptime_decls.items.len});
283 for (ns.comptime_decls.items) |id| {
284 try w.print("* comptime {d}\n", .{@intFromEnum(id)});
285 }
286 try w.print("{d} tests:\n", .{ns.test_decls.items.len});
287 for (ns.test_decls.items) |nav| {
288 try w.print("* nav {d}\n", .{@intFromEnum(nav)});
289 }
290 } else {
291 try w.writeAll("command not found; run 'help' for a command list");
292 }
293}
294
295fn parseIndex(str: []const u8) ?u32 {
296 return std.fmt.parseInt(u32, str, 10) catch null;
297}
298fn parseAnalUnit(str: []const u8) ?AnalUnit {
299 const split_idx = std.mem.indexOfScalar(u8, str, ' ') orelse return null;
300 const kind = str[0..split_idx];
301 const idx_str = str[split_idx + 1 ..];
302 if (std.mem.eql(u8, kind, "comptime")) {
303 return .wrap(.{ .@"comptime" = @enumFromInt(parseIndex(idx_str) orelse return null) });
304 } else if (std.mem.eql(u8, kind, "nav_val")) {
305 return .wrap(.{ .nav_val = @enumFromInt(parseIndex(idx_str) orelse return null) });
306 } else if (std.mem.eql(u8, kind, "nav_ty")) {
307 return .wrap(.{ .nav_ty = @enumFromInt(parseIndex(idx_str) orelse return null) });
308 } else if (std.mem.eql(u8, kind, "type")) {
309 return .wrap(.{ .type = @enumFromInt(parseIndex(idx_str) orelse return null) });
310 } else if (std.mem.eql(u8, kind, "func")) {
311 return .wrap(.{ .func = @enumFromInt(parseIndex(idx_str) orelse return null) });
312 } else if (std.mem.eql(u8, kind, "memoized_state")) {
313 return .wrap(.{ .memoized_state = std.meta.stringToEnum(
314 InternPool.MemoizedStateStage,
315 idx_str,
316 ) orelse return null });
317 } else {
318 return null;
319 }
320}
321fn printAnalUnit(unit: AnalUnit, buf: *[32]u8) []const u8 {
322 const idx: u32 = switch (unit.unwrap()) {
323 .memoized_state => |stage| return std.fmt.bufPrint(buf, "memoized_state {s}", .{@tagName(stage)}) catch unreachable,
324 inline else => |i| @intFromEnum(i),
325 };
326 return std.fmt.bufPrint(buf, "{s} {d}", .{ @tagName(unit.unwrap()), idx }) catch unreachable;
327}
328fn printType(ty: Type, zcu: *const Zcu, w: anytype) !void {
329 const ip = &zcu.intern_pool;
330 switch (ip.indexToKey(ty.toIntern())) {
331 .int_type => |int| try w.print("{c}{d}", .{
332 @as(u8, if (int.signedness == .unsigned) 'u' else 'i'),
333 int.bits,
334 }),
335 .tuple_type => try w.writeAll("(tuple)"),
336 .error_set_type => try w.writeAll("(error set)"),
337 .inferred_error_set_type => try w.writeAll("(inferred error set)"),
338 .func_type => try w.writeAll("(function)"),
339 .anyframe_type => try w.writeAll("(anyframe)"),
340 .vector_type => {
341 try w.print("@Vector({d}, ", .{ty.vectorLen(zcu)});
342 try printType(ty.childType(zcu), zcu, w);
343 try w.writeByte(')');
344 },
345 .array_type => {
346 try w.print("[{d}]", .{ty.arrayLen(zcu)});
347 try printType(ty.childType(zcu), zcu, w);
348 },
349 .opt_type => {
350 try w.writeByte('?');
351 try printType(ty.optionalChild(zcu), zcu, w);
352 },
353 .error_union_type => {
354 try printType(ty.errorUnionSet(zcu), zcu, w);
355 try w.writeByte('!');
356 try printType(ty.errorUnionPayload(zcu), zcu, w);
357 },
358 .ptr_type => {
359 try w.writeAll("*(attrs) ");
360 try printType(ty.childType(zcu), zcu, w);
361 },
362 .simple_type => |simple| try w.writeAll(@tagName(simple)),
363
364 .struct_type,
365 .union_type,
366 .enum_type,
367 .opaque_type,
368 => try w.print("{}[{d}]", .{ ty.containerTypeName(ip).fmt(ip), @intFromEnum(ty.toIntern()) }),
369
370 else => unreachable,
371 }
372}
373
374const std = @import("std");
375const Allocator = std.mem.Allocator;
376
377const Compilation = @import("Compilation.zig");
378const Zcu = @import("Zcu.zig");
379const InternPool = @import("InternPool.zig");
380const Type = @import("Type.zig");
381const AnalUnit = InternPool.AnalUnit;
382
383const IncrementalDebugServer = @This();
src/Sema.zig+24-13
......@@ -2998,11 +2998,7 @@ fn zirStructDecl(
29982998 errdefer pt.destroyNamespace(new_namespace_index);
29992999
30003000 if (pt.zcu.comp.incremental) {
3001 try ip.addDependency(
3002 sema.gpa,
3003 AnalUnit.wrap(.{ .type = wip_ty.index }),
3004 .{ .src_hash = tracked_inst },
3005 );
3001 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
30063002 }
30073003
30083004 const decls = sema.code.bodySlice(extra_index, decls_len);
......@@ -3017,6 +3013,7 @@ fn zirStructDecl(
30173013 }
30183014 try sema.declareDependency(.{ .interned = wip_ty.index });
30193015 try sema.addTypeReferenceEntry(src, wip_ty.index);
3016 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
30203017 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
30213018}
30223019
......@@ -3247,6 +3244,7 @@ fn zirEnumDecl(
32473244
32483245 // We've finished the initial construction of this type, and are about to perform analysis.
32493246 // Set the namespace appropriately, and don't destroy anything on failure.
3247 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
32503248 wip_ty.prepare(ip, new_namespace_index);
32513249 done = true;
32523250
......@@ -3377,11 +3375,7 @@ fn zirUnionDecl(
33773375 errdefer pt.destroyNamespace(new_namespace_index);
33783376
33793377 if (pt.zcu.comp.incremental) {
3380 try zcu.intern_pool.addDependency(
3381 gpa,
3382 AnalUnit.wrap(.{ .type = wip_ty.index }),
3383 .{ .src_hash = tracked_inst },
3384 );
3378 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
33853379 }
33863380
33873381 const decls = sema.code.bodySlice(extra_index, decls_len);
......@@ -3396,6 +3390,7 @@ fn zirUnionDecl(
33963390 }
33973391 try sema.declareDependency(.{ .interned = wip_ty.index });
33983392 try sema.addTypeReferenceEntry(src, wip_ty.index);
3393 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
33993394 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
34003395}
34013396
......@@ -3481,6 +3476,7 @@ fn zirOpaqueDecl(
34813476 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
34823477 }
34833478 try sema.addTypeReferenceEntry(src, wip_ty.index);
3479 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
34843480 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
34853481}
34863482
......@@ -8026,6 +8022,11 @@ fn analyzeCall(
80268022 .generic_owner = func_val.?.toIntern(),
80278023 .comptime_args = comptime_args,
80288024 });
8025 if (zcu.comp.debugIncremental()) {
8026 const nav = ip.indexToKey(func_instance).func.owner_nav;
8027 const gop = try zcu.incremental_debug_state.navs.getOrPut(gpa, nav);
8028 if (!gop.found_existing) gop.value_ptr.* = zcu.generation;
8029 }
80298030
80308031 // This call is problematic as it breaks guarantees about order-independency of semantic analysis.
80318032 // These guarantees are necessary for incremental compilation and parallel semantic analysis.
......@@ -20345,6 +20346,7 @@ fn structInitAnon(
2034520346 if (block.ownerModule().strip) break :codegen_type;
2034620347 try zcu.comp.queueJob(.{ .codegen_type = wip.index });
2034720348 }
20349 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip.index);
2034820350 break :ty wip.finish(ip, new_namespace_index);
2034920351 },
2035020352 .existing => |ty| ty,
......@@ -21406,6 +21408,7 @@ fn zirReify(
2140621408 });
2140721409
2140821410 try sema.addTypeReferenceEntry(src, wip_ty.index);
21411 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
2140921412 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
2141021413 },
2141121414 .@"union" => {
......@@ -21611,6 +21614,7 @@ fn reifyEnum(
2161121614
2161221615 try sema.declareDependency(.{ .interned = wip_ty.index });
2161321616 try sema.addTypeReferenceEntry(src, wip_ty.index);
21617 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
2161421618 wip_ty.prepare(ip, new_namespace_index);
2161521619 wip_ty.setTagTy(ip, tag_ty.toIntern());
2161621620 done = true;
......@@ -21920,6 +21924,7 @@ fn reifyUnion(
2192021924 }
2192121925 try sema.declareDependency(.{ .interned = wip_ty.index });
2192221926 try sema.addTypeReferenceEntry(src, wip_ty.index);
21927 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
2192321928 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
2192421929}
2192521930
......@@ -22273,6 +22278,7 @@ fn reifyStruct(
2227322278 }
2227422279 try sema.declareDependency(.{ .interned = wip_ty.index });
2227522280 try sema.addTypeReferenceEntry(src, wip_ty.index);
22281 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
2227622282 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
2227722283}
2227822284
......@@ -37485,8 +37491,8 @@ fn isKnownZigType(sema: *Sema, ref: Air.Inst.Ref, tag: std.builtin.TypeId) bool
3748537491}
3748637492
3748737493pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
37488 const zcu = sema.pt.zcu;
37489 if (!zcu.comp.incremental) return;
37494 const pt = sema.pt;
37495 if (!pt.zcu.comp.incremental) return;
3749037496
3749137497 const gop = try sema.dependencies.getOrPut(sema.gpa, dependee);
3749237498 if (gop.found_existing) return;
......@@ -37508,7 +37514,7 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
3750837514 else => {},
3750937515 }
3751037516
37511 try zcu.intern_pool.addDependency(sema.gpa, sema.owner, dependee);
37517 try pt.addDependency(sema.owner, dependee);
3751237518}
3751337519
3751437520fn isComptimeMutablePtr(sema: *Sema, val: Value) bool {
......@@ -37905,6 +37911,11 @@ pub fn resolveDeclaredEnum(
3790537911 };
3790637912 defer sema.deinit();
3790737913
37914 if (zcu.comp.debugIncremental()) {
37915 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, sema.owner);
37916 info.last_update_gen = zcu.generation;
37917 }
37918
3790837919 try sema.declareDependency(.{ .src_hash = tracked_inst });
3790937920
3791037921 var block: Block = .{
src/Type.zig+10
......@@ -3797,6 +3797,11 @@ fn resolveStructInner(
37973797 return error.AnalysisFail;
37983798 }
37993799
3800 if (zcu.comp.debugIncremental()) {
3801 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, owner);
3802 info.last_update_gen = zcu.generation;
3803 }
3804
38003805 var analysis_arena = std.heap.ArenaAllocator.init(gpa);
38013806 defer analysis_arena.deinit();
38023807
......@@ -3851,6 +3856,11 @@ fn resolveUnionInner(
38513856 return error.AnalysisFail;
38523857 }
38533858
3859 if (zcu.comp.debugIncremental()) {
3860 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, owner);
3861 info.last_update_gen = zcu.generation;
3862 }
3863
38543864 var analysis_arena = std.heap.ArenaAllocator.init(gpa);
38553865 defer analysis_arena.deinit();
38563866
src/Zcu.zig+52
......@@ -308,8 +308,56 @@ free_type_references: std.ArrayListUnmanaged(u32) = .empty,
308308/// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the element.
309309builtin_decl_values: BuiltinDecl.Memoized = .initFill(.none),
310310
311incremental_debug_state: if (build_options.enable_debug_extensions) IncrementalDebugState else void =
312 if (build_options.enable_debug_extensions) .init else {},
313
311314generation: u32 = 0,
312315
316pub const IncrementalDebugState = struct {
317 /// All container types in the ZCU, even dead ones.
318 /// Value is the generation the type was created on.
319 types: std.AutoArrayHashMapUnmanaged(InternPool.Index, u32),
320 /// All `Nav`s in the ZCU, even dead ones.
321 /// Value is the generation the `Nav` was created on.
322 navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, u32),
323 /// All `AnalUnit`s in the ZCU, even dead ones.
324 units: std.AutoArrayHashMapUnmanaged(AnalUnit, UnitInfo),
325
326 pub const init: IncrementalDebugState = .{
327 .types = .empty,
328 .navs = .empty,
329 .units = .empty,
330 };
331 pub fn deinit(ids: *IncrementalDebugState, gpa: Allocator) void {
332 for (ids.units.values()) |*unit_info| {
333 unit_info.deps.deinit(gpa);
334 }
335 ids.types.deinit(gpa);
336 ids.navs.deinit(gpa);
337 ids.units.deinit(gpa);
338 }
339
340 pub const UnitInfo = struct {
341 last_update_gen: u32,
342 /// This information isn't easily recoverable from `InternPool`'s dependency storage format.
343 deps: std.ArrayListUnmanaged(InternPool.Dependee),
344 };
345 pub fn getUnitInfo(ids: *IncrementalDebugState, gpa: Allocator, unit: AnalUnit) Allocator.Error!*UnitInfo {
346 const gop = try ids.units.getOrPut(gpa, unit);
347 if (!gop.found_existing) gop.value_ptr.* = .{
348 .last_update_gen = std.math.maxInt(u32),
349 .deps = .empty,
350 };
351 return gop.value_ptr;
352 }
353 pub fn newType(ids: *IncrementalDebugState, zcu: *Zcu, ty: InternPool.Index) Allocator.Error!void {
354 try ids.types.putNoClobber(zcu.gpa, ty, zcu.generation);
355 }
356 pub fn newNav(ids: *IncrementalDebugState, zcu: *Zcu, nav: InternPool.Nav.Index) Allocator.Error!void {
357 try ids.navs.putNoClobber(zcu.gpa, nav, zcu.generation);
358 }
359};
360
313361pub const PerThread = @import("Zcu/PerThread.zig");
314362
315363pub const ImportTableAdapter = struct {
......@@ -2746,6 +2794,10 @@ pub fn deinit(zcu: *Zcu) void {
27462794 zcu.free_type_references.deinit(gpa);
27472795
27482796 if (zcu.resolved_references) |*r| r.deinit(gpa);
2797
2798 if (zcu.comp.debugIncremental()) {
2799 zcu.incremental_debug_state.deinit(gpa);
2800 }
27492801 }
27502802 zcu.intern_pool.deinit(gpa);
27512803}
src/Zcu/PerThread.zig+104-47
......@@ -635,6 +635,12 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
635635 if (zcu.builtin_decl_values.get(to_check) != .none) return;
636636 }
637637
638 if (zcu.comp.debugIncremental()) {
639 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, unit);
640 info.last_update_gen = zcu.generation;
641 info.deps.clearRetainingCapacity();
642 }
643
638644 const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed|
639645 .{ any_changed or prev_failed, false }
640646 else |err| switch (err) {
......@@ -784,6 +790,12 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU
784790 return;
785791 }
786792
793 if (zcu.comp.debugIncremental()) {
794 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
795 info.last_update_gen = zcu.generation;
796 info.deps.clearRetainingCapacity();
797 }
798
787799 const unit_prog_node = zcu.sema_prog_node.start("comptime", 0);
788800 defer unit_prog_node.end();
789801
......@@ -958,6 +970,12 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
958970 }
959971 }
960972
973 if (zcu.comp.debugIncremental()) {
974 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
975 info.last_update_gen = zcu.generation;
976 info.deps.clearRetainingCapacity();
977 }
978
961979 const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
962980 defer unit_prog_node.end();
963981
......@@ -1004,6 +1022,35 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
10041022 }
10051023 }
10061024
1025 // If there isn't a type annotation, then we have also just resolved the type. That means the
1026 // the type is up-to-date, so it won't have the chance to mark its own dependency on the value;
1027 // we must do that ourselves.
1028 type_deps_on_val: {
1029 const inst_resolved = nav.analysis.?.zir_index.resolveFull(ip) orelse break :type_deps_on_val;
1030 const file = zcu.fileByIndex(inst_resolved.file);
1031 const zir_decl = file.zir.?.getDeclaration(inst_resolved.inst);
1032 if (zir_decl.type_body != null) break :type_deps_on_val;
1033 // The type does indeed depend on the value. We are responsible for populating all state of
1034 // the `nav_ty`, including exports, references, errors, and dependencies.
1035 const ty_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id });
1036 const ty_was_outdated = zcu.outdated.swapRemove(ty_unit) or
1037 zcu.potentially_outdated.swapRemove(ty_unit);
1038 if (ty_was_outdated) {
1039 _ = zcu.outdated_ready.swapRemove(ty_unit);
1040 zcu.deleteUnitExports(ty_unit);
1041 zcu.deleteUnitReferences(ty_unit);
1042 zcu.deleteUnitCompileLogs(ty_unit);
1043 if (zcu.failed_analysis.fetchSwapRemove(ty_unit)) |kv| {
1044 kv.value.destroy(gpa);
1045 }
1046 _ = zcu.transitive_failed_analysis.swapRemove(ty_unit);
1047 ip.removeDependenciesForDepender(gpa, ty_unit);
1048 }
1049 try pt.addDependency(ty_unit, .{ .nav_val = nav_id });
1050 if (new_failed) try zcu.transitive_failed_analysis.put(gpa, ty_unit, {});
1051 if (ty_was_outdated) try zcu.markDependeeOutdated(.marked_po, .{ .nav_ty = nav_id });
1052 }
1053
10071054 if (new_failed) return error.AnalysisFail;
10081055}
10091056
......@@ -1248,14 +1295,6 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
12481295 // Mark the unit as completed before evaluating the export!
12491296 assert(zcu.analysis_in_progress.swapRemove(anal_unit));
12501297
1251 if (zir_decl.type_body == null) {
1252 // In this situation, it's possible that we were triggered by `analyzeNavType` up the stack. In that
1253 // case, we must also signal that the *type* is now populated to make this export behave correctly.
1254 // An alternative strategy would be to just put something on the job queue to perform the export, but
1255 // this is a little more straightforward, if perhaps less elegant.
1256 _ = zcu.analysis_in_progress.swapRemove(.wrap(.{ .nav_ty = nav_id }));
1257 }
1258
12591298 if (zir_decl.linkage == .@"export") {
12601299 const export_src = block.src(.{ .token_offset = @enumFromInt(@intFromBool(zir_decl.is_pub)) });
12611300 const name_slice = zir.nullTerminatedString(zir_decl.name);
......@@ -1296,6 +1335,18 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc
12961335
12971336 log.debug("ensureNavTypeUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)});
12981337
1338 const type_resolved_by_value: bool = from_val: {
1339 const analysis = nav.analysis orelse break :from_val false;
1340 const inst_resolved = analysis.zir_index.resolveFull(ip) orelse break :from_val false;
1341 const file = zcu.fileByIndex(inst_resolved.file);
1342 const zir_decl = file.zir.?.getDeclaration(inst_resolved.inst);
1343 break :from_val zir_decl.type_body == null;
1344 };
1345 if (type_resolved_by_value) {
1346 // Logic at the end of `ensureNavValUpToDate` is directly responsible for populating our state.
1347 return pt.ensureNavValUpToDate(nav_id);
1348 }
1349
12991350 // Determine whether or not this `Nav`'s type is outdated. This also includes checking if the
13001351 // status is `.unresolved`, which indicates that the value is outdated because it has *never*
13011352 // been analyzed so far.
......@@ -1331,6 +1382,12 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc
13311382 }
13321383 }
13331384
1385 if (zcu.comp.debugIncremental()) {
1386 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
1387 info.last_update_gen = zcu.generation;
1388 info.deps.clearRetainingCapacity();
1389 }
1390
13341391 const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
13351392 defer unit_prog_node.end();
13361393
......@@ -1397,6 +1454,10 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr
13971454 try zcu.analysis_in_progress.put(gpa, anal_unit, {});
13981455 defer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
13991456
1457 const zir_decl = zir.getDeclaration(inst_resolved.inst);
1458 assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace"));
1459 const type_body = zir_decl.type_body.?;
1460
14001461 var analysis_arena: std.heap.ArenaAllocator = .init(gpa);
14011462 defer analysis_arena.deinit();
14021463
......@@ -1436,9 +1497,6 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr
14361497 };
14371498 defer block.instructions.deinit(gpa);
14381499
1439 const zir_decl = zir.getDeclaration(inst_resolved.inst);
1440 assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace"));
1441
14421500 const ty_src = block.src(.{ .node_offset_var_decl_ty = .zero });
14431501
14441502 block.comptime_reason = .{ .reason = .{
......@@ -1446,23 +1504,6 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr
14461504 .r = .{ .simple = .type },
14471505 } };
14481506
1449 const type_body = zir_decl.type_body orelse {
1450 // The type of this `Nav` is inferred from the value.
1451 // In other words, this `nav_ty` depends on the corresponding `nav_val`.
1452 try sema.declareDependency(.{ .nav_val = nav_id });
1453 try pt.ensureNavValUpToDate(nav_id);
1454 // Note that the above call, if it did any work, has removed our `analysis_in_progress` entry for us.
1455 // (Our `defer` will run anyway, but it does nothing in this case.)
1456
1457 // There's not a great way for us to know whether the type actually changed.
1458 // For instance, perhaps the `nav_val` was already up-to-date, but this `nav_ty` is being
1459 // analyzed because this declaration had a type annotation on the *previous* update.
1460 // However, such cases are rare, and it's not unreasonable to re-analyze in them; and in
1461 // other cases where we get here, it's because the `nav_val` was already re-analyzed and
1462 // is outdated.
1463 return .{ .type_changed = true };
1464 };
1465
14661507 const resolved_ty: Type = ty: {
14671508 const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_resolved.inst);
14681509 const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src);
......@@ -1564,6 +1605,12 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
15641605 if (func.analysisUnordered(ip).is_analyzed) return;
15651606 }
15661607
1608 if (zcu.comp.debugIncremental()) {
1609 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
1610 info.last_update_gen = zcu.generation;
1611 info.deps.clearRetainingCapacity();
1612 }
1613
15671614 const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0);
15681615 defer func_prog_node.end();
15691616
......@@ -1816,11 +1863,7 @@ fn createFileRootStruct(
18161863 ip.namespacePtr(namespace_index).owner_type = wip_ty.index;
18171864
18181865 if (zcu.comp.incremental) {
1819 try ip.addDependency(
1820 gpa,
1821 .wrap(.{ .type = wip_ty.index }),
1822 .{ .src_hash = tracked_inst },
1823 );
1866 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
18241867 }
18251868
18261869 try pt.scanNamespace(namespace_index, decls);
......@@ -1832,6 +1875,7 @@ fn createFileRootStruct(
18321875 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
18331876 }
18341877 zcu.setFileRootType(file_index, wip_ty.index);
1878 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
18351879 return wip_ty.finish(ip, namespace_index);
18361880}
18371881
......@@ -2734,10 +2778,11 @@ const ScanDeclIter = struct {
27342778 else => unit: {
27352779 const name = maybe_name.unwrap().?;
27362780 const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name);
2737 const nav = if (existing_unit) |eu|
2738 eu.unwrap().nav_val
2739 else
2740 try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace");
2781 const nav = if (existing_unit) |eu| eu.unwrap().nav_val else nav: {
2782 const nav = try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace");
2783 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav);
2784 break :nav nav;
2785 };
27412786
27422787 const unit: AnalUnit = .wrap(.{ .nav_val = nav });
27432788
......@@ -3911,6 +3956,7 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!
39113956 if (result.new_nav.unwrap()) |nav| {
39123957 // This job depends on any resolve_type_fully jobs queued up before it.
39133958 try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });
3959 if (pt.zcu.comp.debugIncremental()) try pt.zcu.incremental_debug_state.newNav(pt.zcu, nav);
39143960 }
39153961 return result.index;
39163962}
......@@ -3979,6 +4025,12 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
39794025 _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
39804026 zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
39814027
4028 if (zcu.comp.debugIncremental()) {
4029 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
4030 info.last_update_gen = zcu.generation;
4031 info.deps.clearRetainingCapacity();
4032 }
4033
39824034 switch (ip.indexToKey(ty)) {
39834035 .struct_type => return pt.recreateStructType(ty, declared_ty_key),
39844036 .union_type => return pt.recreateUnionType(ty, declared_ty_key),
......@@ -4042,11 +4094,7 @@ fn recreateStructType(
40424094 errdefer wip_ty.cancel(ip, pt.tid);
40434095
40444096 wip_ty.setName(ip, struct_obj.name);
4045 try ip.addDependency(
4046 gpa,
4047 .wrap(.{ .type = wip_ty.index }),
4048 .{ .src_hash = key.zir_index },
4049 );
4097 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
40504098 zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index;
40514099 // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive.
40524100 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
......@@ -4058,6 +4106,7 @@ fn recreateStructType(
40584106 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
40594107 }
40604108
4109 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
40614110 const new_ty = wip_ty.finish(ip, struct_obj.namespace);
40624111 if (inst_info.inst == .main_struct_inst) {
40634112 // This is the root type of a file! Update the reference.
......@@ -4138,11 +4187,7 @@ fn recreateUnionType(
41384187 errdefer wip_ty.cancel(ip, pt.tid);
41394188
41404189 wip_ty.setName(ip, union_obj.name);
4141 try ip.addDependency(
4142 gpa,
4143 .wrap(.{ .type = wip_ty.index }),
4144 .{ .src_hash = key.zir_index },
4145 );
4190 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
41464191 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
41474192 // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive.
41484193 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
......@@ -4154,6 +4199,7 @@ fn recreateUnionType(
41544199 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
41554200 }
41564201
4202 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
41574203 return wip_ty.finish(ip, namespace_index);
41584204}
41594205
......@@ -4255,6 +4301,7 @@ fn recreateEnumType(
42554301 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
42564302 // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive.
42574303
4304 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
42584305 wip_ty.prepare(ip, namespace_index);
42594306 done = true;
42604307
......@@ -4432,3 +4479,13 @@ pub fn refValue(pt: Zcu.PerThread, val: InternPool.Index) Zcu.SemaError!InternPo
44324479 .byte_offset = 0,
44334480 } });
44344481}
4482
4483pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dependee) Allocator.Error!void {
4484 const zcu = pt.zcu;
4485 const gpa = zcu.gpa;
4486 try zcu.intern_pool.addDependency(gpa, unit, dependee);
4487 if (zcu.comp.debugIncremental()) {
4488 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, unit);
4489 try info.deps.append(gpa, dependee);
4490 }
4491}
src/main.zig+28
......@@ -677,6 +677,7 @@ const usage_build_generic =
677677 \\ --debug-compile-errors Crash with helpful diagnostics at the first compile error
678678 \\ --debug-link-snapshot Enable dumping of the linker's state in JSON format
679679 \\ --debug-rt Debug compiler runtime libraries
680 \\ --debug-incremental Enable incremental compilation debug features
680681 \\
681682;
682683
......@@ -832,6 +833,7 @@ fn buildOutputType(
832833 var data_sections = false;
833834 var listen: Listen = .none;
834835 var debug_compile_errors = false;
836 var debug_incremental = false;
835837 var verbose_link = (native_os != .wasi or builtin.link_libc) and
836838 EnvVar.ZIG_VERBOSE_LINK.isSet();
837839 var verbose_cc = (native_os != .wasi or builtin.link_libc) and
......@@ -1383,6 +1385,12 @@ fn buildOutputType(
13831385 }
13841386 } else if (mem.eql(u8, arg, "--debug-rt")) {
13851387 debug_compiler_runtime_libs = true;
1388 } else if (mem.eql(u8, arg, "--debug-incremental")) {
1389 if (build_options.enable_debug_extensions) {
1390 debug_incremental = true;
1391 } else {
1392 warn("Zig was compiled without debug extensions. --debug-incremental has no effect.", .{});
1393 }
13861394 } else if (mem.eql(u8, arg, "-fincremental")) {
13871395 dev.check(.incremental);
13881396 opt_incremental = true;
......@@ -3460,6 +3468,9 @@ fn buildOutputType(
34603468 };
34613469
34623470 const incremental = opt_incremental orelse false;
3471 if (debug_incremental and !incremental) {
3472 fatal("--debug-incremental requires -fincremental", .{});
3473 }
34633474
34643475 const disable_lld_caching = !output_to_cache;
34653476
......@@ -3592,6 +3603,7 @@ fn buildOutputType(
35923603 .cache_mode = cache_mode,
35933604 .subsystem = subsystem,
35943605 .debug_compile_errors = debug_compile_errors,
3606 .debug_incremental = debug_incremental,
35953607 .incremental = incremental,
35963608 .enable_link_snapshots = enable_link_snapshots,
35973609 .install_name = install_name,
......@@ -4195,9 +4207,25 @@ fn serve(
41954207 const main_progress_node = std.Progress.start(.{});
41964208 const file_system_inputs = comp.file_system_inputs.?;
41974209
4210 const IncrementalDebugServer = if (build_options.enable_debug_extensions)
4211 @import("IncrementalDebugServer.zig")
4212 else
4213 void;
4214
4215 var ids: IncrementalDebugServer = if (comp.debugIncremental()) ids: {
4216 break :ids .init(comp.zcu orelse @panic("--debug-incremental requires a ZCU"));
4217 } else undefined;
4218 defer if (comp.debugIncremental()) ids.deinit();
4219
4220 if (comp.debugIncremental()) ids.spawn();
4221
41984222 while (true) {
41994223 const hdr = try server.receiveMessage();
42004224
4225 // Lock the debug server while hanling the message.
4226 if (comp.debugIncremental()) ids.mutex.lock();
4227 defer if (comp.debugIncremental()) ids.mutex.unlock();
4228
42014229 switch (hdr.tag) {
42024230 .exit => return cleanExit(),
42034231 .update => {
test/incremental/dependency_on_type_of_inferred_global created+30
......@@ -0,0 +1,30 @@
1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
4#target=wasm32-wasi-selfhosted
5#update=initial version
6#file=main.zig
7const foo = @as(u8, 123);
8comptime {
9 // depends on value of `foo`
10 if (foo != 123) unreachable;
11}
12comptime {
13 // depends on type of `foo`
14 if (@TypeOf(&foo) != *const u8) unreachable;
15}
16pub fn main() void {}
17#expect_stdout=""
18#update=change the type
19#file=main.zig
20const foo = @as(u16, 123);
21comptime {
22 // depends on value of `foo`
23 if (foo != 123) unreachable;
24}
25comptime {
26 // depends on type of `foo`
27 if (@TypeOf(&foo) != *const u8) unreachable;
28}
29pub fn main() void {}
30#expect_error=main.zig:8:37: error: reached unreachable code