mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
387 lines
17 KiB
Zig
387 lines
17 KiB
Zig
//! 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 and !@import("builtin").single_threaded);
|
|
}
|
|
|
|
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;
|
|
const io = ids.zcu.comp.io;
|
|
|
|
var cmd_buf: [1024]u8 = undefined;
|
|
var text_out: std.ArrayList(u8) = .empty;
|
|
defer text_out.deinit(gpa);
|
|
|
|
const addr: std.Io.net.IpAddress = .{ .ip6 = .loopback(port) };
|
|
var server = addr.listen(io, .{}) catch @panic("IncrementalDebugServer: failed to listen");
|
|
defer server.deinit(io);
|
|
var stream = server.accept(io) catch @panic("IncrementalDebugServer: failed to accept");
|
|
defer stream.close(io);
|
|
|
|
var stream_reader = stream.reader(io, &cmd_buf);
|
|
var stream_writer = stream.writer(io, &.{});
|
|
|
|
while (ids.running.load(.monotonic)) {
|
|
stream_writer.interface.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
|
|
const untrimmed = stream_reader.interface.takeSentinel('\n') catch |err| switch (err) {
|
|
error.EndOfStream => break,
|
|
else => @panic("IncrementalDebugServer: failed to read command"),
|
|
};
|
|
const cmd_and_arg = std.mem.trim(u8, untrimmed, " \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()) {
|
|
stream_writer.interface.writeAll("waiting for in-progress update to finish...\n") catch @panic("IncrementalDebugServer: failed to write");
|
|
ids.mutex.lock();
|
|
}
|
|
defer ids.mutex.unlock();
|
|
var allocating: std.Io.Writer.Allocating = .fromArrayList(gpa, &text_out);
|
|
defer text_out = allocating.toArrayList();
|
|
handleCommand(ids.zcu, &allocating.writer, cmd, arg) catch @panic("IncrementalDebugServer: out of memory");
|
|
}
|
|
text_out.append(gpa, '\n') catch @panic("IncrementalDebugServer: out of memory");
|
|
stream_writer.interface.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, w: *std.Io.Writer, cmd_str: []const u8, arg_str: []const u8) error{ WriteFailed, OutOfMemory }!void {
|
|
const ip = &zcu.intern_pool;
|
|
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: '{f}'
|
|
\\fqn: '{f}'
|
|
\\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("{f}", .{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: '{f}'
|
|
\\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("{f}[{d}]", .{ ty.containerTypeName(ip).fmt(ip), @intFromEnum(ty.toIntern()) }),
|
|
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
const std = @import("std");
|
|
const Io = std.Io;
|
|
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();
|