authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-05-24 22:25:17+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-05-25 04:43:43+01:00
logaeed5f9ebd02b376411f3ee82dd3222f9c3b6e85
tree94dbee0a2039f286612c069b965f0875783121ba
parent35ba8d95a1afd0bebba3c32cf68990f5129fabfe
signaturelock-open Commit is signed but in an unrecognized format.

compiler: introduce incremental debug server

In a compiler built with debug extensions, pass `--debug-incremental` to spawn the "incremental debug server". This is a TCP server exposing a REPL which allows querying a bunch of compiler state, some of which is stored only when that flag is passed. Eventually, this will probably move into `std.zig.Server`/`std.zig.Client`, but this is easier to work with right now. The easiest way to interact with the server is `telnet`.

10 files changed, 576 insertions(+), 32 deletions(-)

lib/compiler/build_runner.zig+2
...@@ -236,6 +236,8 @@ pub fn main() !void {...@@ -236,6 +236,8 @@ pub fn main() !void {
236 graph.debug_compiler_runtime_libs = true;236 graph.debug_compiler_runtime_libs = true;
237 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {237 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
238 builder.debug_compile_errors = true;238 builder.debug_compile_errors = true;
239 } else if (mem.eql(u8, arg, "--debug-incremental")) {
240 builder.debug_incremental = true;
239 } else if (mem.eql(u8, arg, "--system")) {241 } else if (mem.eql(u8, arg, "--system")) {
240 // The usage text shows another argument after this parameter242 // The usage text shows another argument after this parameter
241 // but it is handled by the parent process. The build runner243 // 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,...@@ -59,6 +59,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
59args: ?[]const []const u8 = null,59args: ?[]const []const u8 = null,
60debug_log_scopes: []const []const u8 = &.{},60debug_log_scopes: []const []const u8 = &.{},
61debug_compile_errors: bool = false,61debug_compile_errors: bool = false,
62debug_incremental: bool = false,
62debug_pkg_config: bool = false,63debug_pkg_config: bool = false,
63/// Number of stack frames captured when a `StackTrace` is recorded for debug purposes,64/// Number of stack frames captured when a `StackTrace` is recorded for debug purposes,
64/// in particular at `Step` creation.65/// in particular at `Step` creation.
...@@ -385,6 +386,7 @@ fn createChildOnly(...@@ -385,6 +386,7 @@ fn createChildOnly(
385 .cache_root = parent.cache_root,386 .cache_root = parent.cache_root,
386 .debug_log_scopes = parent.debug_log_scopes,387 .debug_log_scopes = parent.debug_log_scopes,
387 .debug_compile_errors = parent.debug_compile_errors,388 .debug_compile_errors = parent.debug_compile_errors,
389 .debug_incremental = parent.debug_incremental,
388 .debug_pkg_config = parent.debug_pkg_config,390 .debug_pkg_config = parent.debug_pkg_config,
389 .enable_darling = parent.enable_darling,391 .enable_darling = parent.enable_darling,
390 .enable_qemu = parent.enable_qemu,392 .enable_qemu = parent.enable_qemu,
lib/std/Build/Step/Compile.zig+4
...@@ -1447,6 +1447,10 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1447,6 +1447,10 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1447 try zig_args.append("--debug-compile-errors");1447 try zig_args.append("--debug-compile-errors");
1448 }1448 }
14491449
1450 if (b.debug_incremental) {
1451 try zig_args.append("--debug-incremental");
1452 }
1453
1450 if (b.verbose_cimport) try zig_args.append("--verbose-cimport");1454 if (b.verbose_cimport) try zig_args.append("--verbose-cimport");
1451 if (b.verbose_air) try zig_args.append("--verbose-air");1455 if (b.verbose_air) try zig_args.append("--verbose-air");
1452 if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path}));1456 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,...@@ -190,6 +190,8 @@ time_report: bool,
190stack_report: bool,190stack_report: bool,
191debug_compiler_runtime_libs: bool,191debug_compiler_runtime_libs: bool,
192debug_compile_errors: bool,192debug_compile_errors: bool,
193/// Do not check this field directly. Instead, use the `debugIncremental` wrapper function.
194debug_incremental: bool,
193incremental: bool,195incremental: bool,
194alloc_failure_occurred: bool = false,196alloc_failure_occurred: bool = false,
195last_update_was_cache_hit: bool = false,197last_update_was_cache_hit: bool = false,
...@@ -768,6 +770,14 @@ pub const Directories = struct {...@@ -768,6 +770,14 @@ pub const Directories = struct {
768 }770 }
769};771};
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
771pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size;781pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size;
772pub const SemaError = Zcu.SemaError;782pub const SemaError = Zcu.SemaError;
773783
...@@ -1598,6 +1608,7 @@ pub const CreateOptions = struct {...@@ -1598,6 +1608,7 @@ pub const CreateOptions = struct {
1598 verbose_llvm_cpu_features: bool = false,1608 verbose_llvm_cpu_features: bool = false,
1599 debug_compiler_runtime_libs: bool = false,1609 debug_compiler_runtime_libs: bool = false,
1600 debug_compile_errors: bool = false,1610 debug_compile_errors: bool = false,
1611 debug_incremental: bool = false,
1601 incremental: bool = false,1612 incremental: bool = false,
1602 /// Normally when you create a `Compilation`, Zig will automatically build1613 /// Normally when you create a `Compilation`, Zig will automatically build
1603 /// and link in required dependencies, such as compiler-rt and libc. When1614 /// 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...@@ -1968,6 +1979,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1968 .test_name_prefix = options.test_name_prefix,1979 .test_name_prefix = options.test_name_prefix,
1969 .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs,1980 .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs,
1970 .debug_compile_errors = options.debug_compile_errors,1981 .debug_compile_errors = options.debug_compile_errors,
1982 .debug_incremental = options.debug_incremental,
1971 .incremental = options.incremental,1983 .incremental = options.incremental,
1972 .root_name = root_name,1984 .root_name = root_name,
1973 .sysroot = sysroot,1985 .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(...@@ -2998,11 +2998,7 @@ fn zirStructDecl(
2998 errdefer pt.destroyNamespace(new_namespace_index);2998 errdefer pt.destroyNamespace(new_namespace_index);
29992999
3000 if (pt.zcu.comp.incremental) {3000 if (pt.zcu.comp.incremental) {
3001 try ip.addDependency(3001 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
3002 sema.gpa,
3003 AnalUnit.wrap(.{ .type = wip_ty.index }),
3004 .{ .src_hash = tracked_inst },
3005 );
3006 }3002 }
30073003
3008 const decls = sema.code.bodySlice(extra_index, decls_len);3004 const decls = sema.code.bodySlice(extra_index, decls_len);
...@@ -3017,6 +3013,7 @@ fn zirStructDecl(...@@ -3017,6 +3013,7 @@ fn zirStructDecl(
3017 }3013 }
3018 try sema.declareDependency(.{ .interned = wip_ty.index });3014 try sema.declareDependency(.{ .interned = wip_ty.index });
3019 try sema.addTypeReferenceEntry(src, wip_ty.index);3015 try sema.addTypeReferenceEntry(src, wip_ty.index);
3016 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
3020 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));3017 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
3021}3018}
30223019
...@@ -3247,6 +3244,7 @@ fn zirEnumDecl(...@@ -3247,6 +3244,7 @@ fn zirEnumDecl(
32473244
3248 // We've finished the initial construction of this type, and are about to perform analysis.3245 // We've finished the initial construction of this type, and are about to perform analysis.
3249 // Set the namespace appropriately, and don't destroy anything on failure.3246 // 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);
3250 wip_ty.prepare(ip, new_namespace_index);3248 wip_ty.prepare(ip, new_namespace_index);
3251 done = true;3249 done = true;
32523250
...@@ -3377,11 +3375,7 @@ fn zirUnionDecl(...@@ -3377,11 +3375,7 @@ fn zirUnionDecl(
3377 errdefer pt.destroyNamespace(new_namespace_index);3375 errdefer pt.destroyNamespace(new_namespace_index);
33783376
3379 if (pt.zcu.comp.incremental) {3377 if (pt.zcu.comp.incremental) {
3380 try zcu.intern_pool.addDependency(3378 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
3381 gpa,
3382 AnalUnit.wrap(.{ .type = wip_ty.index }),
3383 .{ .src_hash = tracked_inst },
3384 );
3385 }3379 }
33863380
3387 const decls = sema.code.bodySlice(extra_index, decls_len);3381 const decls = sema.code.bodySlice(extra_index, decls_len);
...@@ -3396,6 +3390,7 @@ fn zirUnionDecl(...@@ -3396,6 +3390,7 @@ fn zirUnionDecl(
3396 }3390 }
3397 try sema.declareDependency(.{ .interned = wip_ty.index });3391 try sema.declareDependency(.{ .interned = wip_ty.index });
3398 try sema.addTypeReferenceEntry(src, wip_ty.index);3392 try sema.addTypeReferenceEntry(src, wip_ty.index);
3393 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
3399 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));3394 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
3400}3395}
34013396
...@@ -3481,6 +3476,7 @@ fn zirOpaqueDecl(...@@ -3481,6 +3476,7 @@ fn zirOpaqueDecl(
3481 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });3476 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
3482 }3477 }
3483 try sema.addTypeReferenceEntry(src, wip_ty.index);3478 try sema.addTypeReferenceEntry(src, wip_ty.index);
3479 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
3484 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));3480 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
3485}3481}
34863482
...@@ -8026,6 +8022,11 @@ fn analyzeCall(...@@ -8026,6 +8022,11 @@ fn analyzeCall(
8026 .generic_owner = func_val.?.toIntern(),8022 .generic_owner = func_val.?.toIntern(),
8027 .comptime_args = comptime_args,8023 .comptime_args = comptime_args,
8028 });8024 });
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
8030 // This call is problematic as it breaks guarantees about order-independency of semantic analysis.8031 // This call is problematic as it breaks guarantees about order-independency of semantic analysis.
8031 // These guarantees are necessary for incremental compilation and parallel semantic analysis.8032 // These guarantees are necessary for incremental compilation and parallel semantic analysis.
...@@ -20345,6 +20346,7 @@ fn structInitAnon(...@@ -20345,6 +20346,7 @@ fn structInitAnon(
20345 if (block.ownerModule().strip) break :codegen_type;20346 if (block.ownerModule().strip) break :codegen_type;
20346 try zcu.comp.queueJob(.{ .codegen_type = wip.index });20347 try zcu.comp.queueJob(.{ .codegen_type = wip.index });
20347 }20348 }
20349 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip.index);
20348 break :ty wip.finish(ip, new_namespace_index);20350 break :ty wip.finish(ip, new_namespace_index);
20349 },20351 },
20350 .existing => |ty| ty,20352 .existing => |ty| ty,
...@@ -21406,6 +21408,7 @@ fn zirReify(...@@ -21406,6 +21408,7 @@ fn zirReify(
21406 });21408 });
2140721409
21408 try sema.addTypeReferenceEntry(src, wip_ty.index);21410 try sema.addTypeReferenceEntry(src, wip_ty.index);
21411 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
21409 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));21412 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
21410 },21413 },
21411 .@"union" => {21414 .@"union" => {
...@@ -21611,6 +21614,7 @@ fn reifyEnum(...@@ -21611,6 +21614,7 @@ fn reifyEnum(
2161121614
21612 try sema.declareDependency(.{ .interned = wip_ty.index });21615 try sema.declareDependency(.{ .interned = wip_ty.index });
21613 try sema.addTypeReferenceEntry(src, wip_ty.index);21616 try sema.addTypeReferenceEntry(src, wip_ty.index);
21617 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
21614 wip_ty.prepare(ip, new_namespace_index);21618 wip_ty.prepare(ip, new_namespace_index);
21615 wip_ty.setTagTy(ip, tag_ty.toIntern());21619 wip_ty.setTagTy(ip, tag_ty.toIntern());
21616 done = true;21620 done = true;
...@@ -21920,6 +21924,7 @@ fn reifyUnion(...@@ -21920,6 +21924,7 @@ fn reifyUnion(
21920 }21924 }
21921 try sema.declareDependency(.{ .interned = wip_ty.index });21925 try sema.declareDependency(.{ .interned = wip_ty.index });
21922 try sema.addTypeReferenceEntry(src, wip_ty.index);21926 try sema.addTypeReferenceEntry(src, wip_ty.index);
21927 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
21923 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));21928 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
21924}21929}
2192521930
...@@ -22273,6 +22278,7 @@ fn reifyStruct(...@@ -22273,6 +22278,7 @@ fn reifyStruct(
22273 }22278 }
22274 try sema.declareDependency(.{ .interned = wip_ty.index });22279 try sema.declareDependency(.{ .interned = wip_ty.index });
22275 try sema.addTypeReferenceEntry(src, wip_ty.index);22280 try sema.addTypeReferenceEntry(src, wip_ty.index);
22281 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
22276 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));22282 return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
22277}22283}
2227822284
...@@ -37485,8 +37491,8 @@ fn isKnownZigType(sema: *Sema, ref: Air.Inst.Ref, tag: std.builtin.TypeId) bool...@@ -37485,8 +37491,8 @@ fn isKnownZigType(sema: *Sema, ref: Air.Inst.Ref, tag: std.builtin.TypeId) bool
37485}37491}
3748637492
37487pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {37493pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
37488 const zcu = sema.pt.zcu;37494 const pt = sema.pt;
37489 if (!zcu.comp.incremental) return;37495 if (!pt.zcu.comp.incremental) return;
3749037496
37491 const gop = try sema.dependencies.getOrPut(sema.gpa, dependee);37497 const gop = try sema.dependencies.getOrPut(sema.gpa, dependee);
37492 if (gop.found_existing) return;37498 if (gop.found_existing) return;
...@@ -37508,7 +37514,7 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {...@@ -37508,7 +37514,7 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
37508 else => {},37514 else => {},
37509 }37515 }
3751037516
37511 try zcu.intern_pool.addDependency(sema.gpa, sema.owner, dependee);37517 try pt.addDependency(sema.owner, dependee);
37512}37518}
3751337519
37514fn isComptimeMutablePtr(sema: *Sema, val: Value) bool {37520fn isComptimeMutablePtr(sema: *Sema, val: Value) bool {
...@@ -37905,6 +37911,11 @@ pub fn resolveDeclaredEnum(...@@ -37905,6 +37911,11 @@ pub fn resolveDeclaredEnum(
37905 };37911 };
37906 defer sema.deinit();37912 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
37908 try sema.declareDependency(.{ .src_hash = tracked_inst });37919 try sema.declareDependency(.{ .src_hash = tracked_inst });
3790937920
37910 var block: Block = .{37921 var block: Block = .{
src/Type.zig+10
...@@ -3797,6 +3797,11 @@ fn resolveStructInner(...@@ -3797,6 +3797,11 @@ fn resolveStructInner(
3797 return error.AnalysisFail;3797 return error.AnalysisFail;
3798 }3798 }
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
3800 var analysis_arena = std.heap.ArenaAllocator.init(gpa);3805 var analysis_arena = std.heap.ArenaAllocator.init(gpa);
3801 defer analysis_arena.deinit();3806 defer analysis_arena.deinit();
38023807
...@@ -3851,6 +3856,11 @@ fn resolveUnionInner(...@@ -3851,6 +3856,11 @@ fn resolveUnionInner(
3851 return error.AnalysisFail;3856 return error.AnalysisFail;
3852 }3857 }
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
3854 var analysis_arena = std.heap.ArenaAllocator.init(gpa);3864 var analysis_arena = std.heap.ArenaAllocator.init(gpa);
3855 defer analysis_arena.deinit();3865 defer analysis_arena.deinit();
38563866
src/Zcu.zig+52
...@@ -308,8 +308,56 @@ free_type_references: std.ArrayListUnmanaged(u32) = .empty,...@@ -308,8 +308,56 @@ free_type_references: std.ArrayListUnmanaged(u32) = .empty,
308/// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the element.308/// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the element.
309builtin_decl_values: BuiltinDecl.Memoized = .initFill(.none),309builtin_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
311generation: u32 = 0,314generation: 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
313pub const PerThread = @import("Zcu/PerThread.zig");361pub const PerThread = @import("Zcu/PerThread.zig");
314362
315pub const ImportTableAdapter = struct {363pub const ImportTableAdapter = struct {
...@@ -2746,6 +2794,10 @@ pub fn deinit(zcu: *Zcu) void {...@@ -2746,6 +2794,10 @@ pub fn deinit(zcu: *Zcu) void {
2746 zcu.free_type_references.deinit(gpa);2794 zcu.free_type_references.deinit(gpa);
27472795
2748 if (zcu.resolved_references) |*r| r.deinit(gpa);2796 if (zcu.resolved_references) |*r| r.deinit(gpa);
2797
2798 if (zcu.comp.debugIncremental()) {
2799 zcu.incremental_debug_state.deinit(gpa);
2800 }
2749 }2801 }
2750 zcu.intern_pool.deinit(gpa);2802 zcu.intern_pool.deinit(gpa);
2751}2803}
src/Zcu/PerThread.zig+59-19
...@@ -635,6 +635,12 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized...@@ -635,6 +635,12 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
635 if (zcu.builtin_decl_values.get(to_check) != .none) return;635 if (zcu.builtin_decl_values.get(to_check) != .none) return;
636 }636 }
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
638 const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed|644 const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed|
639 .{ any_changed or prev_failed, false }645 .{ any_changed or prev_failed, false }
640 else |err| switch (err) {646 else |err| switch (err) {
...@@ -784,6 +790,12 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU...@@ -784,6 +790,12 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU
784 return;790 return;
785 }791 }
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
787 const unit_prog_node = zcu.sema_prog_node.start("comptime", 0);799 const unit_prog_node = zcu.sema_prog_node.start("comptime", 0);
788 defer unit_prog_node.end();800 defer unit_prog_node.end();
789801
...@@ -958,6 +970,12 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu...@@ -958,6 +970,12 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
958 }970 }
959 }971 }
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
961 const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);979 const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
962 defer unit_prog_node.end();980 defer unit_prog_node.end();
963981
...@@ -1331,6 +1349,12 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc...@@ -1331,6 +1349,12 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc
1331 }1349 }
1332 }1350 }
13331351
1352 if (zcu.comp.debugIncremental()) {
1353 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
1354 info.last_update_gen = zcu.generation;
1355 info.deps.clearRetainingCapacity();
1356 }
1357
1334 const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);1358 const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
1335 defer unit_prog_node.end();1359 defer unit_prog_node.end();
13361360
...@@ -1564,6 +1588,12 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter...@@ -1564,6 +1588,12 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
1564 if (func.analysisUnordered(ip).is_analyzed) return;1588 if (func.analysisUnordered(ip).is_analyzed) return;
1565 }1589 }
15661590
1591 if (zcu.comp.debugIncremental()) {
1592 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
1593 info.last_update_gen = zcu.generation;
1594 info.deps.clearRetainingCapacity();
1595 }
1596
1567 const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0);1597 const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0);
1568 defer func_prog_node.end();1598 defer func_prog_node.end();
15691599
...@@ -1816,11 +1846,7 @@ fn createFileRootStruct(...@@ -1816,11 +1846,7 @@ fn createFileRootStruct(
1816 ip.namespacePtr(namespace_index).owner_type = wip_ty.index;1846 ip.namespacePtr(namespace_index).owner_type = wip_ty.index;
18171847
1818 if (zcu.comp.incremental) {1848 if (zcu.comp.incremental) {
1819 try ip.addDependency(1849 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
1820 gpa,
1821 .wrap(.{ .type = wip_ty.index }),
1822 .{ .src_hash = tracked_inst },
1823 );
1824 }1850 }
18251851
1826 try pt.scanNamespace(namespace_index, decls);1852 try pt.scanNamespace(namespace_index, decls);
...@@ -1832,6 +1858,7 @@ fn createFileRootStruct(...@@ -1832,6 +1858,7 @@ fn createFileRootStruct(
1832 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });1858 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
1833 }1859 }
1834 zcu.setFileRootType(file_index, wip_ty.index);1860 zcu.setFileRootType(file_index, wip_ty.index);
1861 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
1835 return wip_ty.finish(ip, namespace_index);1862 return wip_ty.finish(ip, namespace_index);
1836}1863}
18371864
...@@ -2734,10 +2761,11 @@ const ScanDeclIter = struct {...@@ -2734,10 +2761,11 @@ const ScanDeclIter = struct {
2734 else => unit: {2761 else => unit: {
2735 const name = maybe_name.unwrap().?;2762 const name = maybe_name.unwrap().?;
2736 const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name);2763 const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name);
2737 const nav = if (existing_unit) |eu|2764 const nav = if (existing_unit) |eu| eu.unwrap().nav_val else nav: {
2738 eu.unwrap().nav_val2765 const nav = try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace");
2739 else2766 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav);
2740 try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace");2767 break :nav nav;
2768 };
27412769
2742 const unit: AnalUnit = .wrap(.{ .nav_val = nav });2770 const unit: AnalUnit = .wrap(.{ .nav_val = nav });
27432771
...@@ -3911,6 +3939,7 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!...@@ -3911,6 +3939,7 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!
3911 if (result.new_nav.unwrap()) |nav| {3939 if (result.new_nav.unwrap()) |nav| {
3912 // This job depends on any resolve_type_fully jobs queued up before it.3940 // This job depends on any resolve_type_fully jobs queued up before it.
3913 try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });3941 try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });
3942 if (pt.zcu.comp.debugIncremental()) try pt.zcu.incremental_debug_state.newNav(pt.zcu, nav);
3914 }3943 }
3915 return result.index;3944 return result.index;
3916}3945}
...@@ -3979,6 +4008,12 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError...@@ -3979,6 +4008,12 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
3979 _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);4008 _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
3980 zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);4009 zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
39814010
4011 if (zcu.comp.debugIncremental()) {
4012 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
4013 info.last_update_gen = zcu.generation;
4014 info.deps.clearRetainingCapacity();
4015 }
4016
3982 switch (ip.indexToKey(ty)) {4017 switch (ip.indexToKey(ty)) {
3983 .struct_type => return pt.recreateStructType(ty, declared_ty_key),4018 .struct_type => return pt.recreateStructType(ty, declared_ty_key),
3984 .union_type => return pt.recreateUnionType(ty, declared_ty_key),4019 .union_type => return pt.recreateUnionType(ty, declared_ty_key),
...@@ -4042,11 +4077,7 @@ fn recreateStructType(...@@ -4042,11 +4077,7 @@ fn recreateStructType(
4042 errdefer wip_ty.cancel(ip, pt.tid);4077 errdefer wip_ty.cancel(ip, pt.tid);
40434078
4044 wip_ty.setName(ip, struct_obj.name);4079 wip_ty.setName(ip, struct_obj.name);
4045 try ip.addDependency(4080 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
4046 gpa,
4047 .wrap(.{ .type = wip_ty.index }),
4048 .{ .src_hash = key.zir_index },
4049 );
4050 zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index;4081 zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index;
4051 // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive.4082 // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive.
4052 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });4083 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
...@@ -4058,6 +4089,7 @@ fn recreateStructType(...@@ -4058,6 +4089,7 @@ fn recreateStructType(
4058 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });4089 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
4059 }4090 }
40604091
4092 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
4061 const new_ty = wip_ty.finish(ip, struct_obj.namespace);4093 const new_ty = wip_ty.finish(ip, struct_obj.namespace);
4062 if (inst_info.inst == .main_struct_inst) {4094 if (inst_info.inst == .main_struct_inst) {
4063 // This is the root type of a file! Update the reference.4095 // This is the root type of a file! Update the reference.
...@@ -4138,11 +4170,7 @@ fn recreateUnionType(...@@ -4138,11 +4170,7 @@ fn recreateUnionType(
4138 errdefer wip_ty.cancel(ip, pt.tid);4170 errdefer wip_ty.cancel(ip, pt.tid);
41394171
4140 wip_ty.setName(ip, union_obj.name);4172 wip_ty.setName(ip, union_obj.name);
4141 try ip.addDependency(4173 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
4142 gpa,
4143 .wrap(.{ .type = wip_ty.index }),
4144 .{ .src_hash = key.zir_index },
4145 );
4146 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;4174 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
4147 // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive.4175 // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive.
4148 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });4176 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
...@@ -4154,6 +4182,7 @@ fn recreateUnionType(...@@ -4154,6 +4182,7 @@ fn recreateUnionType(
4154 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });4182 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
4155 }4183 }
41564184
4185 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
4157 return wip_ty.finish(ip, namespace_index);4186 return wip_ty.finish(ip, namespace_index);
4158}4187}
41594188
...@@ -4255,6 +4284,7 @@ fn recreateEnumType(...@@ -4255,6 +4284,7 @@ fn recreateEnumType(
4255 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;4284 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
4256 // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive.4285 // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive.
42574286
4287 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
4258 wip_ty.prepare(ip, namespace_index);4288 wip_ty.prepare(ip, namespace_index);
4259 done = true;4289 done = true;
42604290
...@@ -4432,3 +4462,13 @@ pub fn refValue(pt: Zcu.PerThread, val: InternPool.Index) Zcu.SemaError!InternPo...@@ -4432,3 +4462,13 @@ pub fn refValue(pt: Zcu.PerThread, val: InternPool.Index) Zcu.SemaError!InternPo
4432 .byte_offset = 0,4462 .byte_offset = 0,
4433 } });4463 } });
4434}4464}
4465
4466pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dependee) Allocator.Error!void {
4467 const zcu = pt.zcu;
4468 const gpa = zcu.gpa;
4469 try zcu.intern_pool.addDependency(gpa, unit, dependee);
4470 if (zcu.comp.debugIncremental()) {
4471 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, unit);
4472 try info.deps.append(gpa, dependee);
4473 }
4474}
src/main.zig+28
...@@ -677,6 +677,7 @@ const usage_build_generic =...@@ -677,6 +677,7 @@ const usage_build_generic =
677 \\ --debug-compile-errors Crash with helpful diagnostics at the first compile error677 \\ --debug-compile-errors Crash with helpful diagnostics at the first compile error
678 \\ --debug-link-snapshot Enable dumping of the linker's state in JSON format678 \\ --debug-link-snapshot Enable dumping of the linker's state in JSON format
679 \\ --debug-rt Debug compiler runtime libraries679 \\ --debug-rt Debug compiler runtime libraries
680 \\ --debug-incremental Enable incremental compilation debug features
680 \\681 \\
681;682;
682683
...@@ -832,6 +833,7 @@ fn buildOutputType(...@@ -832,6 +833,7 @@ fn buildOutputType(
832 var data_sections = false;833 var data_sections = false;
833 var listen: Listen = .none;834 var listen: Listen = .none;
834 var debug_compile_errors = false;835 var debug_compile_errors = false;
836 var debug_incremental = false;
835 var verbose_link = (native_os != .wasi or builtin.link_libc) and837 var verbose_link = (native_os != .wasi or builtin.link_libc) and
836 EnvVar.ZIG_VERBOSE_LINK.isSet();838 EnvVar.ZIG_VERBOSE_LINK.isSet();
837 var verbose_cc = (native_os != .wasi or builtin.link_libc) and839 var verbose_cc = (native_os != .wasi or builtin.link_libc) and
...@@ -1383,6 +1385,12 @@ fn buildOutputType(...@@ -1383,6 +1385,12 @@ fn buildOutputType(
1383 }1385 }
1384 } else if (mem.eql(u8, arg, "--debug-rt")) {1386 } else if (mem.eql(u8, arg, "--debug-rt")) {
1385 debug_compiler_runtime_libs = true;1387 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 }
1386 } else if (mem.eql(u8, arg, "-fincremental")) {1394 } else if (mem.eql(u8, arg, "-fincremental")) {
1387 dev.check(.incremental);1395 dev.check(.incremental);
1388 opt_incremental = true;1396 opt_incremental = true;
...@@ -3460,6 +3468,9 @@ fn buildOutputType(...@@ -3460,6 +3468,9 @@ fn buildOutputType(
3460 };3468 };
34613469
3462 const incremental = opt_incremental orelse false;3470 const incremental = opt_incremental orelse false;
3471 if (debug_incremental and !incremental) {
3472 fatal("--debug-incremental requires -fincremental", .{});
3473 }
34633474
3464 const disable_lld_caching = !output_to_cache;3475 const disable_lld_caching = !output_to_cache;
34653476
...@@ -3592,6 +3603,7 @@ fn buildOutputType(...@@ -3592,6 +3603,7 @@ fn buildOutputType(
3592 .cache_mode = cache_mode,3603 .cache_mode = cache_mode,
3593 .subsystem = subsystem,3604 .subsystem = subsystem,
3594 .debug_compile_errors = debug_compile_errors,3605 .debug_compile_errors = debug_compile_errors,
3606 .debug_incremental = debug_incremental,
3595 .incremental = incremental,3607 .incremental = incremental,
3596 .enable_link_snapshots = enable_link_snapshots,3608 .enable_link_snapshots = enable_link_snapshots,
3597 .install_name = install_name,3609 .install_name = install_name,
...@@ -4195,9 +4207,25 @@ fn serve(...@@ -4195,9 +4207,25 @@ fn serve(
4195 const main_progress_node = std.Progress.start(.{});4207 const main_progress_node = std.Progress.start(.{});
4196 const file_system_inputs = comp.file_system_inputs.?;4208 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
4198 while (true) {4222 while (true) {
4199 const hdr = try server.receiveMessage();4223 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
4201 switch (hdr.tag) {4229 switch (hdr.tag) {
4202 .exit => return cleanExit(),4230 .exit => return cleanExit(),
4203 .update => {4231 .update => {