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`.
This commit is contained in:
mlugg 2025-05-24 22:25:17 +01:00
parent 35ba8d95a1
commit aeed5f9ebd
No known key found for this signature in database
GPG key ID: 3F5B7DCCBF4AF02E
10 changed files with 576 additions and 32 deletions

View file

@ -236,6 +236,8 @@ pub fn main() !void {
graph.debug_compiler_runtime_libs = true; graph.debug_compiler_runtime_libs = true;
} else if (mem.eql(u8, arg, "--debug-compile-errors")) { } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
builder.debug_compile_errors = true; builder.debug_compile_errors = true;
} else if (mem.eql(u8, arg, "--debug-incremental")) {
builder.debug_incremental = true;
} else if (mem.eql(u8, arg, "--system")) { } else if (mem.eql(u8, arg, "--system")) {
// The usage text shows another argument after this parameter // The usage text shows another argument after this parameter
// but it is handled by the parent process. The build runner // but it is handled by the parent process. The build runner

View file

@ -59,6 +59,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
args: ?[]const []const u8 = null, args: ?[]const []const u8 = null,
debug_log_scopes: []const []const u8 = &.{}, debug_log_scopes: []const []const u8 = &.{},
debug_compile_errors: bool = false, debug_compile_errors: bool = false,
debug_incremental: bool = false,
debug_pkg_config: bool = false, debug_pkg_config: bool = false,
/// Number of stack frames captured when a `StackTrace` is recorded for debug purposes, /// Number of stack frames captured when a `StackTrace` is recorded for debug purposes,
/// in particular at `Step` creation. /// in particular at `Step` creation.
@ -385,6 +386,7 @@ fn createChildOnly(
.cache_root = parent.cache_root, .cache_root = parent.cache_root,
.debug_log_scopes = parent.debug_log_scopes, .debug_log_scopes = parent.debug_log_scopes,
.debug_compile_errors = parent.debug_compile_errors, .debug_compile_errors = parent.debug_compile_errors,
.debug_incremental = parent.debug_incremental,
.debug_pkg_config = parent.debug_pkg_config, .debug_pkg_config = parent.debug_pkg_config,
.enable_darling = parent.enable_darling, .enable_darling = parent.enable_darling,
.enable_qemu = parent.enable_qemu, .enable_qemu = parent.enable_qemu,

View file

@ -1447,6 +1447,10 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
try zig_args.append("--debug-compile-errors"); try zig_args.append("--debug-compile-errors");
} }
if (b.debug_incremental) {
try zig_args.append("--debug-incremental");
}
if (b.verbose_cimport) try zig_args.append("--verbose-cimport"); if (b.verbose_cimport) try zig_args.append("--verbose-cimport");
if (b.verbose_air) try zig_args.append("--verbose-air"); if (b.verbose_air) try zig_args.append("--verbose-air");
if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path})); if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path}));

View file

@ -190,6 +190,8 @@ time_report: bool,
stack_report: bool, stack_report: bool,
debug_compiler_runtime_libs: bool, debug_compiler_runtime_libs: bool,
debug_compile_errors: bool, debug_compile_errors: bool,
/// Do not check this field directly. Instead, use the `debugIncremental` wrapper function.
debug_incremental: bool,
incremental: bool, incremental: bool,
alloc_failure_occurred: bool = false, alloc_failure_occurred: bool = false,
last_update_was_cache_hit: bool = false, last_update_was_cache_hit: bool = false,
@ -768,6 +770,14 @@ pub const Directories = struct {
} }
}; };
/// This small wrapper function just checks whether debug extensions are enabled before checking
/// `comp.debug_incremental`. It is inline so that comptime-known `false` propagates to the caller,
/// preventing debugging features from making it into release builds of the compiler.
pub inline fn debugIncremental(comp: *const Compilation) bool {
if (!build_options.enable_debug_extensions) return false;
return comp.debug_incremental;
}
pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size; pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size;
pub const SemaError = Zcu.SemaError; pub const SemaError = Zcu.SemaError;
@ -1598,6 +1608,7 @@ pub const CreateOptions = struct {
verbose_llvm_cpu_features: bool = false, verbose_llvm_cpu_features: bool = false,
debug_compiler_runtime_libs: bool = false, debug_compiler_runtime_libs: bool = false,
debug_compile_errors: bool = false, debug_compile_errors: bool = false,
debug_incremental: bool = false,
incremental: bool = false, incremental: bool = false,
/// Normally when you create a `Compilation`, Zig will automatically build /// Normally when you create a `Compilation`, Zig will automatically build
/// and link in required dependencies, such as compiler-rt and libc. When /// 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
.test_name_prefix = options.test_name_prefix, .test_name_prefix = options.test_name_prefix,
.debug_compiler_runtime_libs = options.debug_compiler_runtime_libs, .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs,
.debug_compile_errors = options.debug_compile_errors, .debug_compile_errors = options.debug_compile_errors,
.debug_incremental = options.debug_incremental,
.incremental = options.incremental, .incremental = options.incremental,
.root_name = root_name, .root_name = root_name,
.sysroot = sysroot, .sysroot = sysroot,

View file

@ -0,0 +1,383 @@
//! This is a simple TCP server which exposes a REPL useful for debugging incremental compilation
//! issues. Eventually, this logic should move into `std.zig.Client`/`std.zig.Server` or something
//! similar, but for now, this works. The server is enabled by the '--debug-incremental' CLI flag.
//! The easiest way to interact with the REPL is to use `telnet`:
//! ```
//! telnet "::1" 7623
//! ```
//! 'help' will list available commands. When the debug server is enabled, the compiler tracks a lot
//! of extra state (see `Zcu.IncrementalDebugState`), so note that RSS will be higher than usual.
comptime {
// This file should only be referenced when debug extensions are enabled.
std.debug.assert(@import("build_options").enable_debug_extensions);
}
zcu: *Zcu,
thread: ?std.Thread,
running: std.atomic.Value(bool),
/// Held by our owner when an update is in-progress, and held by us when responding to a command.
/// So, essentially guards all access to `Compilation`, including `Zcu`.
mutex: std.Thread.Mutex,
pub fn init(zcu: *Zcu) IncrementalDebugServer {
return .{
.zcu = zcu,
.thread = null,
.running = .init(true),
.mutex = .{},
};
}
pub fn deinit(ids: *IncrementalDebugServer) void {
if (ids.thread) |t| {
ids.running.store(false, .monotonic);
t.join();
}
}
const port = 7623;
pub fn spawn(ids: *IncrementalDebugServer) void {
std.debug.print("spawning incremental debug server on port {d}\n", .{port});
ids.thread = std.Thread.spawn(.{ .allocator = ids.zcu.comp.arena }, runThread, .{ids}) catch |err|
std.process.fatal("failed to spawn incremental debug server: {s}", .{@errorName(err)});
}
fn runThread(ids: *IncrementalDebugServer) void {
const gpa = ids.zcu.gpa;
var cmd_buf: [1024]u8 = undefined;
var text_out: std.ArrayListUnmanaged(u8) = .empty;
defer text_out.deinit(gpa);
const addr = std.net.Address.parseIp6("::", port) catch unreachable;
var server = addr.listen(.{}) catch @panic("IncrementalDebugServer: failed to listen");
defer server.deinit();
const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");
defer conn.stream.close();
while (ids.running.load(.monotonic)) {
conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
var fbs = std.io.fixedBufferStream(&cmd_buf);
conn.stream.reader().streamUntilDelimiter(fbs.writer(), '\n', cmd_buf.len) catch |err| switch (err) {
error.EndOfStream => break,
else => @panic("IncrementalDebugServer: failed to read command"),
};
const cmd_and_arg = std.mem.trim(u8, fbs.getWritten(), " \t\r\n");
const cmd: []const u8, const arg: []const u8 = if (std.mem.indexOfScalar(u8, cmd_and_arg, ' ')) |i|
.{ cmd_and_arg[0..i], cmd_and_arg[i + 1 ..] }
else
.{ cmd_and_arg, "" };
text_out.clearRetainingCapacity();
{
if (!ids.mutex.tryLock()) {
conn.stream.writeAll("waiting for in-progress update to finish...\n") catch @panic("IncrementalDebugServer: failed to write");
ids.mutex.lock();
}
defer ids.mutex.unlock();
handleCommand(ids.zcu, &text_out, cmd, arg) catch @panic("IncrementalDebugServer: out of memory");
}
text_out.append(gpa, '\n') catch @panic("IncrementalDebugServer: out of memory");
conn.stream.writeAll(text_out.items) catch @panic("IncrementalDebugServer: failed to write");
}
std.debug.print("closing incremental debug server\n", .{});
}
const help_str: []const u8 =
\\[str] arguments are any string.
\\[id] arguments are a numeric ID/index, like an InternPool index.
\\[unit] arguments are strings like 'func 1234' where '1234' is the relevant index (in this case an InternPool index).
\\
\\MISC
\\ summary
\\ Dump some information about the whole ZCU.
\\ nav_info [id]
\\ Dump basic info about a NAV.
\\
\\SEARCHING
\\ find_type [str]
\\ Find types (including dead ones) whose names contain the given substring.
\\ Starting with '^' or ending with '$' anchors to the start/end of the name.
\\ find_nav [str]
\\ Find NAVs (including dead ones) whose names contain the given substring.
\\ Starting with '^' or ending with '$' anchors to the start/end of the name.
\\
\\UNITS
\\ unit_info [unit]
\\ Dump basic info about an analysis unit.
\\ unit_dependencies [unit]
\\ List all units which an analysis unit depends on.
\\ unit_trace [unit]
\\ Dump the current reference trace of an analysis unit.
\\
\\TYPES
\\ type_info [id]
\\ Dump basic info about a type.
\\ type_namespace [id]
\\ List all declarations in the namespace of a type.
\\
;
fn handleCommand(zcu: *Zcu, output: *std.ArrayListUnmanaged(u8), cmd_str: []const u8, arg_str: []const u8) Allocator.Error!void {
const ip = &zcu.intern_pool;
const gpa = zcu.gpa;
const w = output.writer(gpa);
if (std.mem.eql(u8, cmd_str, "help")) {
try w.writeAll(help_str);
} else if (std.mem.eql(u8, cmd_str, "summary")) {
try w.print(
\\last generation: {d}
\\total container types: {d}
\\total NAVs: {d}
\\total units: {d}
\\
, .{
zcu.generation - 1,
zcu.incremental_debug_state.types.count(),
zcu.incremental_debug_state.navs.count(),
zcu.incremental_debug_state.units.count(),
});
} else if (std.mem.eql(u8, cmd_str, "nav_info")) {
const nav_index: InternPool.Nav.Index = @enumFromInt(parseIndex(arg_str) orelse return w.writeAll("malformed nav index"));
const create_gen = zcu.incremental_debug_state.navs.get(nav_index) orelse return w.writeAll("unknown nav index");
const nav = ip.getNav(nav_index);
try w.print(
\\name: '{}'
\\fqn: '{}'
\\status: {s}
\\created on generation: {d}
\\
, .{
nav.name.fmt(ip),
nav.fqn.fmt(ip),
@tagName(nav.status),
create_gen,
});
switch (nav.status) {
.unresolved => {},
.type_resolved, .fully_resolved => {
try w.writeAll("type: ");
try printType(.fromInterned(nav.typeOf(ip)), zcu, w);
try w.writeByte('\n');
},
}
} else if (std.mem.eql(u8, cmd_str, "find_type")) {
if (arg_str.len == 0) return w.writeAll("bad usage");
const anchor_start = arg_str[0] == '^';
const anchor_end = arg_str[arg_str.len - 1] == '$';
const query = arg_str[@intFromBool(anchor_start) .. arg_str.len - @intFromBool(anchor_end)];
var num_results: usize = 0;
for (zcu.incremental_debug_state.types.keys()) |type_ip_index| {
const ty: Type = .fromInterned(type_ip_index);
const ty_name = ty.containerTypeName(ip).toSlice(ip);
const success = switch (@as(u2, @intFromBool(anchor_start)) << 1 | @intFromBool(anchor_end)) {
0b00 => std.mem.indexOf(u8, ty_name, query) != null,
0b01 => std.mem.endsWith(u8, ty_name, query),
0b10 => std.mem.startsWith(u8, ty_name, query),
0b11 => std.mem.eql(u8, ty_name, query),
};
if (success) {
num_results += 1;
try w.print("* type {d} ('{s}')\n", .{ @intFromEnum(type_ip_index), ty_name });
}
}
try w.print("Found {d} results\n", .{num_results});
} else if (std.mem.eql(u8, cmd_str, "find_nav")) {
if (arg_str.len == 0) return w.writeAll("bad usage");
const anchor_start = arg_str[0] == '^';
const anchor_end = arg_str[arg_str.len - 1] == '$';
const query = arg_str[@intFromBool(anchor_start) .. arg_str.len - @intFromBool(anchor_end)];
var num_results: usize = 0;
for (zcu.incremental_debug_state.navs.keys()) |nav_index| {
const nav = ip.getNav(nav_index);
const nav_fqn = nav.fqn.toSlice(ip);
const success = switch (@as(u2, @intFromBool(anchor_start)) << 1 | @intFromBool(anchor_end)) {
0b00 => std.mem.indexOf(u8, nav_fqn, query) != null,
0b01 => std.mem.endsWith(u8, nav_fqn, query),
0b10 => std.mem.startsWith(u8, nav_fqn, query),
0b11 => std.mem.eql(u8, nav_fqn, query),
};
if (success) {
num_results += 1;
try w.print("* nav {d} ('{s}')\n", .{ @intFromEnum(nav_index), nav_fqn });
}
}
try w.print("Found {d} results\n", .{num_results});
} else if (std.mem.eql(u8, cmd_str, "unit_info")) {
const unit = parseAnalUnit(arg_str) orelse return w.writeAll("malformed anal unit");
const unit_info = zcu.incremental_debug_state.units.get(unit) orelse return w.writeAll("unknown anal unit");
var ref_str_buf: [32]u8 = undefined;
const ref_str: []const u8 = ref: {
const refs = try zcu.resolveReferences();
const ref = refs.get(unit) orelse break :ref "<unreferenced>";
const referencer = (ref orelse break :ref "<analysis root>").referencer;
break :ref printAnalUnit(referencer, &ref_str_buf);
};
const has_err: []const u8 = err: {
if (zcu.failed_analysis.contains(unit)) break :err "true";
if (zcu.transitive_failed_analysis.contains(unit)) break :err "true (transitive)";
break :err "false";
};
try w.print(
\\last update generation: {d}
\\current referencer: {s}
\\has error: {s}
\\
, .{
unit_info.last_update_gen,
ref_str,
has_err,
});
} else if (std.mem.eql(u8, cmd_str, "unit_dependencies")) {
const unit = parseAnalUnit(arg_str) orelse return w.writeAll("malformed anal unit");
const unit_info = zcu.incremental_debug_state.units.get(unit) orelse return w.writeAll("unknown anal unit");
for (unit_info.deps.items, 0..) |dependee, i| {
try w.print("[{d}] ", .{i});
switch (dependee) {
.src_hash, .namespace, .namespace_name, .zon_file, .embed_file => try w.print("{}", .{zcu.fmtDependee(dependee)}),
.nav_val, .nav_ty => |nav| try w.print("{s} {d}", .{ @tagName(dependee), @intFromEnum(nav) }),
.interned => |ip_index| switch (ip.indexToKey(ip_index)) {
.struct_type, .union_type, .enum_type => try w.print("type {d}", .{@intFromEnum(ip_index)}),
.func => try w.print("func {d}", .{@intFromEnum(ip_index)}),
else => unreachable,
},
.memoized_state => |stage| try w.print("memoized_state {s}", .{@tagName(stage)}),
}
try w.writeByte('\n');
}
} else if (std.mem.eql(u8, cmd_str, "unit_trace")) {
const unit = parseAnalUnit(arg_str) orelse return w.writeAll("malformed anal unit");
if (!zcu.incremental_debug_state.units.contains(unit)) return w.writeAll("unknown anal unit");
const refs = try zcu.resolveReferences();
if (!refs.contains(unit)) return w.writeAll("not referenced");
var opt_cur: ?AnalUnit = unit;
while (opt_cur) |cur| {
var buf: [32]u8 = undefined;
try w.print("* {s}\n", .{printAnalUnit(cur, &buf)});
opt_cur = if (refs.get(cur).?) |ref| ref.referencer else null;
}
} else if (std.mem.eql(u8, cmd_str, "type_info")) {
const ip_index: InternPool.Index = @enumFromInt(parseIndex(arg_str) orelse return w.writeAll("malformed ip index"));
const create_gen = zcu.incremental_debug_state.types.get(ip_index) orelse return w.writeAll("unknown type");
try w.print(
\\name: '{}'
\\created on generation: {d}
\\
, .{
Type.fromInterned(ip_index).containerTypeName(ip).fmt(ip),
create_gen,
});
} else if (std.mem.eql(u8, cmd_str, "type_namespace")) {
const ip_index: InternPool.Index = @enumFromInt(parseIndex(arg_str) orelse return w.writeAll("malformed ip index"));
if (!zcu.incremental_debug_state.types.contains(ip_index)) return w.writeAll("unknown type");
const ns = zcu.namespacePtr(Type.fromInterned(ip_index).getNamespaceIndex(zcu));
try w.print("{d} pub decls:\n", .{ns.pub_decls.count()});
for (ns.pub_decls.keys()) |nav| {
try w.print("* nav {d}\n", .{@intFromEnum(nav)});
}
try w.print("{d} non-pub decls:\n", .{ns.priv_decls.count()});
for (ns.priv_decls.keys()) |nav| {
try w.print("* nav {d}\n", .{@intFromEnum(nav)});
}
try w.print("{d} comptime decls:\n", .{ns.comptime_decls.items.len});
for (ns.comptime_decls.items) |id| {
try w.print("* comptime {d}\n", .{@intFromEnum(id)});
}
try w.print("{d} tests:\n", .{ns.test_decls.items.len});
for (ns.test_decls.items) |nav| {
try w.print("* nav {d}\n", .{@intFromEnum(nav)});
}
} else {
try w.writeAll("command not found; run 'help' for a command list");
}
}
fn parseIndex(str: []const u8) ?u32 {
return std.fmt.parseInt(u32, str, 10) catch null;
}
fn parseAnalUnit(str: []const u8) ?AnalUnit {
const split_idx = std.mem.indexOfScalar(u8, str, ' ') orelse return null;
const kind = str[0..split_idx];
const idx_str = str[split_idx + 1 ..];
if (std.mem.eql(u8, kind, "comptime")) {
return .wrap(.{ .@"comptime" = @enumFromInt(parseIndex(idx_str) orelse return null) });
} else if (std.mem.eql(u8, kind, "nav_val")) {
return .wrap(.{ .nav_val = @enumFromInt(parseIndex(idx_str) orelse return null) });
} else if (std.mem.eql(u8, kind, "nav_ty")) {
return .wrap(.{ .nav_ty = @enumFromInt(parseIndex(idx_str) orelse return null) });
} else if (std.mem.eql(u8, kind, "type")) {
return .wrap(.{ .type = @enumFromInt(parseIndex(idx_str) orelse return null) });
} else if (std.mem.eql(u8, kind, "func")) {
return .wrap(.{ .func = @enumFromInt(parseIndex(idx_str) orelse return null) });
} else if (std.mem.eql(u8, kind, "memoized_state")) {
return .wrap(.{ .memoized_state = std.meta.stringToEnum(
InternPool.MemoizedStateStage,
idx_str,
) orelse return null });
} else {
return null;
}
}
fn printAnalUnit(unit: AnalUnit, buf: *[32]u8) []const u8 {
const idx: u32 = switch (unit.unwrap()) {
.memoized_state => |stage| return std.fmt.bufPrint(buf, "memoized_state {s}", .{@tagName(stage)}) catch unreachable,
inline else => |i| @intFromEnum(i),
};
return std.fmt.bufPrint(buf, "{s} {d}", .{ @tagName(unit.unwrap()), idx }) catch unreachable;
}
fn printType(ty: Type, zcu: *const Zcu, w: anytype) !void {
const ip = &zcu.intern_pool;
switch (ip.indexToKey(ty.toIntern())) {
.int_type => |int| try w.print("{c}{d}", .{
@as(u8, if (int.signedness == .unsigned) 'u' else 'i'),
int.bits,
}),
.tuple_type => try w.writeAll("(tuple)"),
.error_set_type => try w.writeAll("(error set)"),
.inferred_error_set_type => try w.writeAll("(inferred error set)"),
.func_type => try w.writeAll("(function)"),
.anyframe_type => try w.writeAll("(anyframe)"),
.vector_type => {
try w.print("@Vector({d}, ", .{ty.vectorLen(zcu)});
try printType(ty.childType(zcu), zcu, w);
try w.writeByte(')');
},
.array_type => {
try w.print("[{d}]", .{ty.arrayLen(zcu)});
try printType(ty.childType(zcu), zcu, w);
},
.opt_type => {
try w.writeByte('?');
try printType(ty.optionalChild(zcu), zcu, w);
},
.error_union_type => {
try printType(ty.errorUnionSet(zcu), zcu, w);
try w.writeByte('!');
try printType(ty.errorUnionPayload(zcu), zcu, w);
},
.ptr_type => {
try w.writeAll("*(attrs) ");
try printType(ty.childType(zcu), zcu, w);
},
.simple_type => |simple| try w.writeAll(@tagName(simple)),
.struct_type,
.union_type,
.enum_type,
.opaque_type,
=> try w.print("{}[{d}]", .{ ty.containerTypeName(ip).fmt(ip), @intFromEnum(ty.toIntern()) }),
else => unreachable,
}
}
const std = @import("std");
const Allocator = std.mem.Allocator;
const Compilation = @import("Compilation.zig");
const Zcu = @import("Zcu.zig");
const InternPool = @import("InternPool.zig");
const Type = @import("Type.zig");
const AnalUnit = InternPool.AnalUnit;
const IncrementalDebugServer = @This();

View file

@ -2998,11 +2998,7 @@ fn zirStructDecl(
errdefer pt.destroyNamespace(new_namespace_index); errdefer pt.destroyNamespace(new_namespace_index);
if (pt.zcu.comp.incremental) { if (pt.zcu.comp.incremental) {
try ip.addDependency( try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
sema.gpa,
AnalUnit.wrap(.{ .type = wip_ty.index }),
.{ .src_hash = tracked_inst },
);
} }
const decls = sema.code.bodySlice(extra_index, decls_len); const decls = sema.code.bodySlice(extra_index, decls_len);
@ -3017,6 +3013,7 @@ fn zirStructDecl(
} }
try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index); try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
} }
@ -3247,6 +3244,7 @@ fn zirEnumDecl(
// We've finished the initial construction of this type, and are about to perform analysis. // We've finished the initial construction of this type, and are about to perform analysis.
// Set the namespace appropriately, and don't destroy anything on failure. // Set the namespace appropriately, and don't destroy anything on failure.
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
wip_ty.prepare(ip, new_namespace_index); wip_ty.prepare(ip, new_namespace_index);
done = true; done = true;
@ -3377,11 +3375,7 @@ fn zirUnionDecl(
errdefer pt.destroyNamespace(new_namespace_index); errdefer pt.destroyNamespace(new_namespace_index);
if (pt.zcu.comp.incremental) { if (pt.zcu.comp.incremental) {
try zcu.intern_pool.addDependency( try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
gpa,
AnalUnit.wrap(.{ .type = wip_ty.index }),
.{ .src_hash = tracked_inst },
);
} }
const decls = sema.code.bodySlice(extra_index, decls_len); const decls = sema.code.bodySlice(extra_index, decls_len);
@ -3396,6 +3390,7 @@ fn zirUnionDecl(
} }
try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index); try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
} }
@ -3481,6 +3476,7 @@ fn zirOpaqueDecl(
try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
} }
try sema.addTypeReferenceEntry(src, wip_ty.index); try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
} }
@ -8026,6 +8022,11 @@ fn analyzeCall(
.generic_owner = func_val.?.toIntern(), .generic_owner = func_val.?.toIntern(),
.comptime_args = comptime_args, .comptime_args = comptime_args,
}); });
if (zcu.comp.debugIncremental()) {
const nav = ip.indexToKey(func_instance).func.owner_nav;
const gop = try zcu.incremental_debug_state.navs.getOrPut(gpa, nav);
if (!gop.found_existing) gop.value_ptr.* = zcu.generation;
}
// This call is problematic as it breaks guarantees about order-independency of semantic analysis. // This call is problematic as it breaks guarantees about order-independency of semantic analysis.
// These guarantees are necessary for incremental compilation and parallel semantic analysis. // These guarantees are necessary for incremental compilation and parallel semantic analysis.
@ -20345,6 +20346,7 @@ fn structInitAnon(
if (block.ownerModule().strip) break :codegen_type; if (block.ownerModule().strip) break :codegen_type;
try zcu.comp.queueJob(.{ .codegen_type = wip.index }); try zcu.comp.queueJob(.{ .codegen_type = wip.index });
} }
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip.index);
break :ty wip.finish(ip, new_namespace_index); break :ty wip.finish(ip, new_namespace_index);
}, },
.existing => |ty| ty, .existing => |ty| ty,
@ -21406,6 +21408,7 @@ fn zirReify(
}); });
try sema.addTypeReferenceEntry(src, wip_ty.index); try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
}, },
.@"union" => { .@"union" => {
@ -21611,6 +21614,7 @@ fn reifyEnum(
try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index); try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
wip_ty.prepare(ip, new_namespace_index); wip_ty.prepare(ip, new_namespace_index);
wip_ty.setTagTy(ip, tag_ty.toIntern()); wip_ty.setTagTy(ip, tag_ty.toIntern());
done = true; done = true;
@ -21920,6 +21924,7 @@ fn reifyUnion(
} }
try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index); try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
} }
@ -22273,6 +22278,7 @@ fn reifyStruct(
} }
try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index); try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
} }
@ -37485,8 +37491,8 @@ fn isKnownZigType(sema: *Sema, ref: Air.Inst.Ref, tag: std.builtin.TypeId) bool
} }
pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
const zcu = sema.pt.zcu; const pt = sema.pt;
if (!zcu.comp.incremental) return; if (!pt.zcu.comp.incremental) return;
const gop = try sema.dependencies.getOrPut(sema.gpa, dependee); const gop = try sema.dependencies.getOrPut(sema.gpa, dependee);
if (gop.found_existing) return; if (gop.found_existing) return;
@ -37508,7 +37514,7 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
else => {}, else => {},
} }
try zcu.intern_pool.addDependency(sema.gpa, sema.owner, dependee); try pt.addDependency(sema.owner, dependee);
} }
fn isComptimeMutablePtr(sema: *Sema, val: Value) bool { fn isComptimeMutablePtr(sema: *Sema, val: Value) bool {
@ -37905,6 +37911,11 @@ pub fn resolveDeclaredEnum(
}; };
defer sema.deinit(); defer sema.deinit();
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, sema.owner);
info.last_update_gen = zcu.generation;
}
try sema.declareDependency(.{ .src_hash = tracked_inst }); try sema.declareDependency(.{ .src_hash = tracked_inst });
var block: Block = .{ var block: Block = .{

View file

@ -3797,6 +3797,11 @@ fn resolveStructInner(
return error.AnalysisFail; return error.AnalysisFail;
} }
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, owner);
info.last_update_gen = zcu.generation;
}
var analysis_arena = std.heap.ArenaAllocator.init(gpa); var analysis_arena = std.heap.ArenaAllocator.init(gpa);
defer analysis_arena.deinit(); defer analysis_arena.deinit();
@ -3851,6 +3856,11 @@ fn resolveUnionInner(
return error.AnalysisFail; return error.AnalysisFail;
} }
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, owner);
info.last_update_gen = zcu.generation;
}
var analysis_arena = std.heap.ArenaAllocator.init(gpa); var analysis_arena = std.heap.ArenaAllocator.init(gpa);
defer analysis_arena.deinit(); defer analysis_arena.deinit();

View file

@ -308,8 +308,56 @@ free_type_references: std.ArrayListUnmanaged(u32) = .empty,
/// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the element. /// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the element.
builtin_decl_values: BuiltinDecl.Memoized = .initFill(.none), builtin_decl_values: BuiltinDecl.Memoized = .initFill(.none),
incremental_debug_state: if (build_options.enable_debug_extensions) IncrementalDebugState else void =
if (build_options.enable_debug_extensions) .init else {},
generation: u32 = 0, generation: u32 = 0,
pub const IncrementalDebugState = struct {
/// All container types in the ZCU, even dead ones.
/// Value is the generation the type was created on.
types: std.AutoArrayHashMapUnmanaged(InternPool.Index, u32),
/// All `Nav`s in the ZCU, even dead ones.
/// Value is the generation the `Nav` was created on.
navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, u32),
/// All `AnalUnit`s in the ZCU, even dead ones.
units: std.AutoArrayHashMapUnmanaged(AnalUnit, UnitInfo),
pub const init: IncrementalDebugState = .{
.types = .empty,
.navs = .empty,
.units = .empty,
};
pub fn deinit(ids: *IncrementalDebugState, gpa: Allocator) void {
for (ids.units.values()) |*unit_info| {
unit_info.deps.deinit(gpa);
}
ids.types.deinit(gpa);
ids.navs.deinit(gpa);
ids.units.deinit(gpa);
}
pub const UnitInfo = struct {
last_update_gen: u32,
/// This information isn't easily recoverable from `InternPool`'s dependency storage format.
deps: std.ArrayListUnmanaged(InternPool.Dependee),
};
pub fn getUnitInfo(ids: *IncrementalDebugState, gpa: Allocator, unit: AnalUnit) Allocator.Error!*UnitInfo {
const gop = try ids.units.getOrPut(gpa, unit);
if (!gop.found_existing) gop.value_ptr.* = .{
.last_update_gen = std.math.maxInt(u32),
.deps = .empty,
};
return gop.value_ptr;
}
pub fn newType(ids: *IncrementalDebugState, zcu: *Zcu, ty: InternPool.Index) Allocator.Error!void {
try ids.types.putNoClobber(zcu.gpa, ty, zcu.generation);
}
pub fn newNav(ids: *IncrementalDebugState, zcu: *Zcu, nav: InternPool.Nav.Index) Allocator.Error!void {
try ids.navs.putNoClobber(zcu.gpa, nav, zcu.generation);
}
};
pub const PerThread = @import("Zcu/PerThread.zig"); pub const PerThread = @import("Zcu/PerThread.zig");
pub const ImportTableAdapter = struct { pub const ImportTableAdapter = struct {
@ -2746,6 +2794,10 @@ pub fn deinit(zcu: *Zcu) void {
zcu.free_type_references.deinit(gpa); zcu.free_type_references.deinit(gpa);
if (zcu.resolved_references) |*r| r.deinit(gpa); if (zcu.resolved_references) |*r| r.deinit(gpa);
if (zcu.comp.debugIncremental()) {
zcu.incremental_debug_state.deinit(gpa);
}
} }
zcu.intern_pool.deinit(gpa); zcu.intern_pool.deinit(gpa);
} }

View file

@ -635,6 +635,12 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
if (zcu.builtin_decl_values.get(to_check) != .none) return; if (zcu.builtin_decl_values.get(to_check) != .none) return;
} }
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, unit);
info.last_update_gen = zcu.generation;
info.deps.clearRetainingCapacity();
}
const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed| const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed|
.{ any_changed or prev_failed, false } .{ any_changed or prev_failed, false }
else |err| switch (err) { else |err| switch (err) {
@ -784,6 +790,12 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU
return; return;
} }
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
info.last_update_gen = zcu.generation;
info.deps.clearRetainingCapacity();
}
const unit_prog_node = zcu.sema_prog_node.start("comptime", 0); const unit_prog_node = zcu.sema_prog_node.start("comptime", 0);
defer unit_prog_node.end(); defer unit_prog_node.end();
@ -958,6 +970,12 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
} }
} }
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
info.last_update_gen = zcu.generation;
info.deps.clearRetainingCapacity();
}
const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
defer unit_prog_node.end(); defer unit_prog_node.end();
@ -1331,6 +1349,12 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc
} }
} }
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
info.last_update_gen = zcu.generation;
info.deps.clearRetainingCapacity();
}
const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
defer unit_prog_node.end(); defer unit_prog_node.end();
@ -1564,6 +1588,12 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
if (func.analysisUnordered(ip).is_analyzed) return; if (func.analysisUnordered(ip).is_analyzed) return;
} }
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
info.last_update_gen = zcu.generation;
info.deps.clearRetainingCapacity();
}
const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0); const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0);
defer func_prog_node.end(); defer func_prog_node.end();
@ -1816,11 +1846,7 @@ fn createFileRootStruct(
ip.namespacePtr(namespace_index).owner_type = wip_ty.index; ip.namespacePtr(namespace_index).owner_type = wip_ty.index;
if (zcu.comp.incremental) { if (zcu.comp.incremental) {
try ip.addDependency( try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
gpa,
.wrap(.{ .type = wip_ty.index }),
.{ .src_hash = tracked_inst },
);
} }
try pt.scanNamespace(namespace_index, decls); try pt.scanNamespace(namespace_index, decls);
@ -1832,6 +1858,7 @@ fn createFileRootStruct(
try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
} }
zcu.setFileRootType(file_index, wip_ty.index); zcu.setFileRootType(file_index, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return wip_ty.finish(ip, namespace_index); return wip_ty.finish(ip, namespace_index);
} }
@ -2734,10 +2761,11 @@ const ScanDeclIter = struct {
else => unit: { else => unit: {
const name = maybe_name.unwrap().?; const name = maybe_name.unwrap().?;
const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name); const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name);
const nav = if (existing_unit) |eu| const nav = if (existing_unit) |eu| eu.unwrap().nav_val else nav: {
eu.unwrap().nav_val const nav = try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace");
else if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav);
try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace"); break :nav nav;
};
const unit: AnalUnit = .wrap(.{ .nav_val = nav }); const unit: AnalUnit = .wrap(.{ .nav_val = nav });
@ -3911,6 +3939,7 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!
if (result.new_nav.unwrap()) |nav| { if (result.new_nav.unwrap()) |nav| {
// This job depends on any resolve_type_fully jobs queued up before it. // This job depends on any resolve_type_fully jobs queued up before it.
try pt.zcu.comp.queueJob(.{ .codegen_nav = nav }); try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });
if (pt.zcu.comp.debugIncremental()) try pt.zcu.incremental_debug_state.newNav(pt.zcu, nav);
} }
return result.index; return result.index;
} }
@ -3979,6 +4008,12 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
_ = zcu.transitive_failed_analysis.swapRemove(anal_unit); _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
info.last_update_gen = zcu.generation;
info.deps.clearRetainingCapacity();
}
switch (ip.indexToKey(ty)) { switch (ip.indexToKey(ty)) {
.struct_type => return pt.recreateStructType(ty, declared_ty_key), .struct_type => return pt.recreateStructType(ty, declared_ty_key),
.union_type => return pt.recreateUnionType(ty, declared_ty_key), .union_type => return pt.recreateUnionType(ty, declared_ty_key),
@ -4042,11 +4077,7 @@ fn recreateStructType(
errdefer wip_ty.cancel(ip, pt.tid); errdefer wip_ty.cancel(ip, pt.tid);
wip_ty.setName(ip, struct_obj.name); wip_ty.setName(ip, struct_obj.name);
try ip.addDependency( try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
gpa,
.wrap(.{ .type = wip_ty.index }),
.{ .src_hash = key.zir_index },
);
zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index; zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index;
// No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive. // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive.
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
@ -4058,6 +4089,7 @@ fn recreateStructType(
try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
} }
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
const new_ty = wip_ty.finish(ip, struct_obj.namespace); const new_ty = wip_ty.finish(ip, struct_obj.namespace);
if (inst_info.inst == .main_struct_inst) { if (inst_info.inst == .main_struct_inst) {
// This is the root type of a file! Update the reference. // This is the root type of a file! Update the reference.
@ -4138,11 +4170,7 @@ fn recreateUnionType(
errdefer wip_ty.cancel(ip, pt.tid); errdefer wip_ty.cancel(ip, pt.tid);
wip_ty.setName(ip, union_obj.name); wip_ty.setName(ip, union_obj.name);
try ip.addDependency( try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index });
gpa,
.wrap(.{ .type = wip_ty.index }),
.{ .src_hash = key.zir_index },
);
zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
// No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive. // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive.
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
@ -4154,6 +4182,7 @@ fn recreateUnionType(
try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
} }
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return wip_ty.finish(ip, namespace_index); return wip_ty.finish(ip, namespace_index);
} }
@ -4255,6 +4284,7 @@ fn recreateEnumType(
zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
// No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive. // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive.
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
wip_ty.prepare(ip, namespace_index); wip_ty.prepare(ip, namespace_index);
done = true; done = true;
@ -4432,3 +4462,13 @@ pub fn refValue(pt: Zcu.PerThread, val: InternPool.Index) Zcu.SemaError!InternPo
.byte_offset = 0, .byte_offset = 0,
} }); } });
} }
pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dependee) Allocator.Error!void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
try zcu.intern_pool.addDependency(gpa, unit, dependee);
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, unit);
try info.deps.append(gpa, dependee);
}
}

View file

@ -677,6 +677,7 @@ const usage_build_generic =
\\ --debug-compile-errors Crash with helpful diagnostics at the first compile error \\ --debug-compile-errors Crash with helpful diagnostics at the first compile error
\\ --debug-link-snapshot Enable dumping of the linker's state in JSON format \\ --debug-link-snapshot Enable dumping of the linker's state in JSON format
\\ --debug-rt Debug compiler runtime libraries \\ --debug-rt Debug compiler runtime libraries
\\ --debug-incremental Enable incremental compilation debug features
\\ \\
; ;
@ -832,6 +833,7 @@ fn buildOutputType(
var data_sections = false; var data_sections = false;
var listen: Listen = .none; var listen: Listen = .none;
var debug_compile_errors = false; var debug_compile_errors = false;
var debug_incremental = false;
var verbose_link = (native_os != .wasi or builtin.link_libc) and var verbose_link = (native_os != .wasi or builtin.link_libc) and
EnvVar.ZIG_VERBOSE_LINK.isSet(); EnvVar.ZIG_VERBOSE_LINK.isSet();
var verbose_cc = (native_os != .wasi or builtin.link_libc) and var verbose_cc = (native_os != .wasi or builtin.link_libc) and
@ -1383,6 +1385,12 @@ fn buildOutputType(
} }
} else if (mem.eql(u8, arg, "--debug-rt")) { } else if (mem.eql(u8, arg, "--debug-rt")) {
debug_compiler_runtime_libs = true; debug_compiler_runtime_libs = true;
} else if (mem.eql(u8, arg, "--debug-incremental")) {
if (build_options.enable_debug_extensions) {
debug_incremental = true;
} else {
warn("Zig was compiled without debug extensions. --debug-incremental has no effect.", .{});
}
} else if (mem.eql(u8, arg, "-fincremental")) { } else if (mem.eql(u8, arg, "-fincremental")) {
dev.check(.incremental); dev.check(.incremental);
opt_incremental = true; opt_incremental = true;
@ -3460,6 +3468,9 @@ fn buildOutputType(
}; };
const incremental = opt_incremental orelse false; const incremental = opt_incremental orelse false;
if (debug_incremental and !incremental) {
fatal("--debug-incremental requires -fincremental", .{});
}
const disable_lld_caching = !output_to_cache; const disable_lld_caching = !output_to_cache;
@ -3592,6 +3603,7 @@ fn buildOutputType(
.cache_mode = cache_mode, .cache_mode = cache_mode,
.subsystem = subsystem, .subsystem = subsystem,
.debug_compile_errors = debug_compile_errors, .debug_compile_errors = debug_compile_errors,
.debug_incremental = debug_incremental,
.incremental = incremental, .incremental = incremental,
.enable_link_snapshots = enable_link_snapshots, .enable_link_snapshots = enable_link_snapshots,
.install_name = install_name, .install_name = install_name,
@ -4195,9 +4207,25 @@ fn serve(
const main_progress_node = std.Progress.start(.{}); const main_progress_node = std.Progress.start(.{});
const file_system_inputs = comp.file_system_inputs.?; const file_system_inputs = comp.file_system_inputs.?;
const IncrementalDebugServer = if (build_options.enable_debug_extensions)
@import("IncrementalDebugServer.zig")
else
void;
var ids: IncrementalDebugServer = if (comp.debugIncremental()) ids: {
break :ids .init(comp.zcu orelse @panic("--debug-incremental requires a ZCU"));
} else undefined;
defer if (comp.debugIncremental()) ids.deinit();
if (comp.debugIncremental()) ids.spawn();
while (true) { while (true) {
const hdr = try server.receiveMessage(); const hdr = try server.receiveMessage();
// Lock the debug server while hanling the message.
if (comp.debugIncremental()) ids.mutex.lock();
defer if (comp.debugIncremental()) ids.mutex.unlock();
switch (hdr.tag) { switch (hdr.tag) {
.exit => return cleanExit(), .exit => return cleanExit(),
.update => { .update => {