1pub const enabled = switch (build_options.io_mode) {
2 .threaded => build_options.enable_debug_extensions,
3 .evented => false, // would use threadlocals in a way incompatible with evented
4};
5
6/// We override the panic implementation to our own one, so we can print our own information before
7/// calling the default panic handler. This declaration must be re-exposed from `@import("root")`.
8pub const panic = std.debug.FullPanic(if (enabled) panicImpl else std.debug.defaultPanic);
9
10/// We let std install its segfault handler, but we override the target-agnostic handler it calls,
11/// so we can print our own information before calling the default segfault logic. This declaration
12/// must be re-exposed from `@import("root")`.
13pub const debug = if (enabled) struct {
14 pub const handleSegfault = handleSegfaultImpl;
15} else struct {};
16
17/// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command.
18/// Set by `main` as soon as arguments are known. The value here is a default in case we somehow
19/// crash earlier than that.
20pub var zig_argv0: []const u8 = "zig";
21
22fn handleSegfaultImpl(addr: ?usize, name: []const u8, opt_ctx: ?std.debug.CpuContextPtr) noreturn {
23 @branchHint(.cold);
24 dumpCrashContext() catch {};
25 std.debug.defaultHandleSegfault(addr, name, opt_ctx);
26}
27fn panicImpl(msg: []const u8, first_trace_addr: ?usize) noreturn {
28 @branchHint(.cold);
29 dumpCrashContext() catch {};
30 std.debug.defaultPanic(msg, first_trace_addr orelse @returnAddress());
31}
32
33pub const AnalyzeBody = if (enabled) struct {
34 parent: ?*AnalyzeBody,
35 sema: *Sema,
36 block: *Sema.Block,
37 body: []const Zir.Inst.Index,
38 body_index: usize,
39
40 threadlocal var current: ?*AnalyzeBody = null;
41
42 pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void {
43 ab.body_index = index;
44 }
45
46 pub fn push(ab: *AnalyzeBody, sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) void {
47 ab.* = .{
48 .parent = current,
49 .sema = sema,
50 .block = block,
51 .body = body,
52 .body_index = 0,
53 };
54 current = ab;
55 }
56 pub fn pop(ab: *AnalyzeBody) void {
57 std.debug.assert(current.? == ab); // `Sema.analyzeBodyInner` did not match push/pop calls
58 current = ab.parent;
59 }
60} else struct {
61 const current: ?noreturn = null;
62 // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls.
63 pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {}
64 pub inline fn pop(_: AnalyzeBody) void {}
65 pub inline fn setBodyIndex(_: @This(), _: usize) void {}
66};
67
68pub const CodegenFunc = if (enabled) struct {
69 zcu: *const Zcu,
70 func_index: InternPool.Index,
71 threadlocal var current: ?CodegenFunc = null;
72 pub fn start(zcu: *const Zcu, func_index: InternPool.Index) void {
73 std.debug.assert(current == null);
74 current = .{ .zcu = zcu, .func_index = func_index };
75 }
76 pub fn stop(func_index: InternPool.Index) void {
77 std.debug.assert(current.?.func_index == func_index);
78 current = null;
79 }
80} else struct {
81 const current: ?noreturn = null;
82 // Dummy implementation
83 pub fn start(_: *const Zcu, _: InternPool.Index) void {}
84 pub fn stop(_: InternPool.Index) void {}
85};
86
87pub const LinkerOp = if (enabled) struct {
88 lf: *link.File,
89 tid: Zcu.PerThread.Id,
90 threadlocal var current: ?LinkerOp = null;
91 pub fn start(lf: *link.File, tid: Zcu.PerThread.Id) void {
92 std.debug.assert(current == null);
93 current = .{ .lf = lf, .tid = tid };
94 }
95 pub fn stop(lf: *link.File, tid: Zcu.PerThread.Id) void {
96 std.debug.assert(current.?.lf == lf and current.?.tid == tid);
97 current = null;
98 }
99} else struct {
100 const current: ?noreturn = null;
101 // Dummy implementation
102 pub fn start(_: *link.File, _: Zcu.PerThread.Id) void {}
103 pub fn stop(_: *link.File, _: Zcu.PerThread.Id) void {}
104};
105
106fn dumpCrashContext() Io.Writer.Error!void {
107 const S = struct {
108 /// In the case of recursive panics or segfaults, don't print the context for a second time.
109 threadlocal var already_dumped = false;
110 /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator,
111 /// but that shouldn't be necessary---it's already only used in one place.
112 threadlocal var crash_heap: [64 * 1024]u8 = undefined;
113 };
114 if (S.already_dumped) return;
115 S.already_dumped = true;
116
117 std.Options.debug_io.vtable.crashHandler(std.Options.debug_io.userdata);
118
119 // TODO: this does mean that a different thread could grab the stderr mutex between the context
120 // and the actual panic printing, which would be quite confusing.
121 const stderr = std.debug.lockStderr(&.{});
122 defer std.debug.unlockStderr();
123 const w = &stderr.file_writer.interface;
124
125 try w.writeAll("Compiler crash context:\n");
126
127 if (CodegenFunc.current) |*cg| {
128 const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav;
129 const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn;
130 try w.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
131 } else if (AnalyzeBody.current) |anal| {
132 try dumpCrashContextSema(anal, w, &S.crash_heap);
133 } else if (LinkerOp.current) |linker_op| {
134 try w.writeAll("Linker snapshot:\n");
135 switch (try linker_op.lf.dump(w, linker_op.tid)) {
136 .unimplemented => try w.writeAll("(backend does not support link snapshots)"),
137 .needs_extensions => try w.writeAll("(build with -Ddebug-extensions to dump linker state)"),
138 .disabled => try w.writeAll("(run with --debug-link-snapshot to dump linker state)"),
139 .enabled => {},
140 }
141 try w.writeAll("\n\n");
142 } else {
143 try w.writeAll("(no context)\n\n");
144 }
145}
146fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void {
147 const block: *Sema.Block = anal.block;
148 const zcu = anal.sema.pt.zcu;
149 const comp = zcu.comp;
150
151 var fba: std.heap.FixedBufferAllocator = .init(crash_heap);
152
153 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse {
154 const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool));
155 try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(comp)});
156 return;
157 };
158
159 try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)});
160
161 print_zir.renderInstructionContext(
162 fba.allocator(),
163 anal.body,
164 anal.body_index,
165 file,
166 src_base_node,
167 6, // indent
168 stderr,
169 ) catch |err| switch (err) {
170 error.OutOfMemory => try stderr.writeAll(" <out of memory dumping zir>\n"),
171 else => |e| return e,
172 };
173 try stderr.print(
174 \\ For full context, use the command
175 \\ {s} ast-check -t {f}
176 \\
177 \\
178 , .{ zig_argv0, file.path.fmt(comp) });
179
180 var parent = anal.parent;
181 while (parent) |curr| {
182 fba.reset();
183 const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool));
184 try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(comp)});
185 _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse {
186 try stderr.writeAll(" > [lost instruction; this should not happen]\n");
187 parent = curr.parent;
188 continue;
189 };
190 try stderr.writeAll(" > ");
191 print_zir.renderSingleInstruction(
192 fba.allocator(),
193 curr.body[curr.body_index],
194 cur_block_file,
195 cur_block_src_base_node,
196 6, // indent
197 stderr,
198 ) catch |err| switch (err) {
199 error.OutOfMemory => try stderr.writeAll(" <out of memory dumping zir>\n"),
200 else => |e| return e,
201 };
202 try stderr.writeAll("\n");
203
204 parent = curr.parent;
205 }
206
207 try stderr.writeByte('\n');
208}
209
210const std = @import("std");
211const Io = std.Io;
212const Zir = std.zig.Zir;
213
214const Sema = @import("Sema.zig");
215const Zcu = @import("Zcu.zig");
216const link = @import("link.zig");
217const InternPool = @import("InternPool.zig");
218const dev = @import("dev.zig");
219const print_zir = @import("print_zir.zig");
220
221const build_options = @import("build_options");