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 {
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+59-19
......@@ -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
......@@ -1331,6 +1349,12 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc
13311349 }
13321350 }
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
13341358 const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
13351359 defer unit_prog_node.end();
13361360
......@@ -1564,6 +1588,12 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
15641588 if (func.analysisUnordered(ip).is_analyzed) return;
15651589 }
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
15671597 const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0);
15681598 defer func_prog_node.end();
15691599
......@@ -1816,11 +1846,7 @@ fn createFileRootStruct(
18161846 ip.namespacePtr(namespace_index).owner_type = wip_ty.index;
18171847
18181848 if (zcu.comp.incremental) {
1819 try ip.addDependency(
1820 gpa,
1821 .wrap(.{ .type = wip_ty.index }),
1822 .{ .src_hash = tracked_inst },
1823 );
1849 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
18241850 }
18251851
18261852 try pt.scanNamespace(namespace_index, decls);
......@@ -1832,6 +1858,7 @@ fn createFileRootStruct(
18321858 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
18331859 }
18341860 zcu.setFileRootType(file_index, wip_ty.index);
1861 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
18351862 return wip_ty.finish(ip, namespace_index);
18361863}
18371864
......@@ -2734,10 +2761,11 @@ const ScanDeclIter = struct {
27342761 else => unit: {
27352762 const name = maybe_name.unwrap().?;
27362763 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");
2764 const nav = if (existing_unit) |eu| eu.unwrap().nav_val else nav: {
2765 const nav = try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace");
2766 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav);
2767 break :nav nav;
2768 };
27412769
27422770 const unit: AnalUnit = .wrap(.{ .nav_val = nav });
27432771
......@@ -3911,6 +3939,7 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!
39113939 if (result.new_nav.unwrap()) |nav| {
39123940 // This job depends on any resolve_type_fully jobs queued up before it.
39133941 try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });
3942 if (pt.zcu.comp.debugIncremental()) try pt.zcu.incremental_debug_state.newNav(pt.zcu, nav);
39143943 }
39153944 return result.index;
39163945}
......@@ -3979,6 +4008,12 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
39794008 _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
39804009 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
39824017 switch (ip.indexToKey(ty)) {
39834018 .struct_type => return pt.recreateStructType(ty, declared_ty_key),
39844019 .union_type => return pt.recreateUnionType(ty, declared_ty_key),
......@@ -4042,11 +4077,7 @@ fn recreateStructType(
40424077 errdefer wip_ty.cancel(ip, pt.tid);
40434078
40444079 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 );
4080 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
40504081 zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index;
40514082 // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive.
40524083 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
......@@ -4058,6 +4089,7 @@ fn recreateStructType(
40584089 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
40594090 }
40604091
4092 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
40614093 const new_ty = wip_ty.finish(ip, struct_obj.namespace);
40624094 if (inst_info.inst == .main_struct_inst) {
40634095 // This is the root type of a file! Update the reference.
......@@ -4138,11 +4170,7 @@ fn recreateUnionType(
41384170 errdefer wip_ty.cancel(ip, pt.tid);
41394171
41404172 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 );
4173 try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
41464174 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
41474175 // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive.
41484176 try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
......@@ -4154,6 +4182,7 @@ fn recreateUnionType(
41544182 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
41554183 }
41564184
4185 if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
41574186 return wip_ty.finish(ip, namespace_index);
41584187}
41594188
......@@ -4255,6 +4284,7 @@ fn recreateEnumType(
42554284 zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
42564285 // 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);
42584288 wip_ty.prepare(ip, namespace_index);
42594289 done = true;
42604290
......@@ -4432,3 +4462,13 @@ pub fn refValue(pt: Zcu.PerThread, val: InternPool.Index) Zcu.SemaError!InternPo
44324462 .byte_offset = 0,
44334463 } });
44344464}
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 =
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 => {