authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-08 14:32:02+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:52+01:00
logd9661e9e05af7a4be31c17cbfbbd8bc44d7c9000
tree3a13bbd3391f31707089ba5f3f54569fe131612f
parent3a561da38d42ba331eb67bdb7d86d4a3e9b74533
signaturelock-open Commit is signed but in an unrecognized format.

compiler: better crash handler

Far simpler, because everything which `crash_report.zig` did is now handled pretty well by `std.debug` anyway. All we want is to print some context around panics and segfaults. Using the new ability to override the default segfault handler while still having std handle the target-specific bits for us, that's really simple.

4 files changed, 117 insertions(+), 467 deletions(-)

src/Sema.zig+3-3
......@@ -1130,8 +1130,8 @@ fn analyzeBodyInner(
11301130 const tags = sema.code.instructions.items(.tag);
11311131 const datas = sema.code.instructions.items(.data);
11321132
1133 var crash_info = crash_report.prepAnalyzeBody(sema, block, body);
1134 crash_info.push();
1133 var crash_info: crash_report.AnalyzeBody = undefined;
1134 crash_info.push(sema, block, body);
11351135 defer crash_info.pop();
11361136
11371137 // We use a while (true) loop here to avoid a redundant way of breaking out of
......@@ -2632,7 +2632,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg
26322632 std.debug.print("compile error during Sema:\n", .{});
26332633 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
26342634 error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
2635 crash_report.compilerPanic("unexpected compile error occurred", null);
2635 std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{});
26362636 }
26372637
26382638 if (block) |start_block| {
src/Zcu/PerThread.zig+4
......@@ -28,6 +28,7 @@ const Value = @import("../Value.zig");
2828const Zcu = @import("../Zcu.zig");
2929const Compilation = @import("../Compilation.zig");
3030const codegen = @import("../codegen.zig");
31const crash_report = @import("../crash_report.zig");
3132const Zir = std.zig.Zir;
3233const Zoir = std.zig.Zoir;
3334const ZonGen = std.zig.ZonGen;
......@@ -4390,6 +4391,9 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep
43904391pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void {
43914392 const zcu = pt.zcu;
43924393
4394 crash_report.CodegenFunc.start(zcu, func_index);
4395 defer crash_report.CodegenFunc.stop(func_index);
4396
43934397 var timer = zcu.comp.startTimer();
43944398
43954399 const success: bool = if (runCodegenInner(pt, func_index, air)) |mir| success: {
src/crash_report.zig+107-461
......@@ -1,34 +1,31 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const build_options = @import("build_options");
4const debug = std.debug;
5const print_zir = @import("print_zir.zig");
6const windows = std.os.windows;
7const posix = std.posix;
8const native_os = builtin.os.tag;
9
10const Zcu = @import("Zcu.zig");
11const Sema = @import("Sema.zig");
12const InternPool = @import("InternPool.zig");
13const Zir = std.zig.Zir;
14const Decl = Zcu.Decl;
15const dev = @import("dev.zig");
16
17/// To use these crash report diagnostics, publish this panic in your main file
18/// and add `pub const enable_segfault_handler = false;` to your `std_options`.
19/// You will also need to call initialize() on startup, preferably as the very first operation in your program.
20pub const panic = if (build_options.enable_debug_extensions)
21 std.debug.FullPanic(compilerPanic)
22else if (dev.env == .bootstrap)
1/// We override the panic implementation to our own one, so we can print our own information before
2/// calling the default panic handler. This declaration must be re-exposed from `@import("root")`.
3pub const panic = if (dev.env == .bootstrap)
234 std.debug.simple_panic
245else
25 std.debug.FullPanic(std.debug.defaultPanic);
6 std.debug.FullPanic(panicImpl);
267
27/// Install signal handlers to identify crashes and report diagnostics.
28pub fn initialize() void {
29 if (build_options.enable_debug_extensions and debug.have_segfault_handling_support) {
30 attachSegfaultHandler();
31 }
8/// We let std install its segfault handler, but we override the target-agnostic handler it calls,
9/// so we can print our own information before calling the default segfault logic. This declaration
10/// must be re-exposed from `@import("root")`.
11pub const debug = struct {
12 pub const handleSegfault = handleSegfaultImpl;
13};
14
15/// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command.
16/// Set by `main` as soon as arguments are known. The value here is a default in case we somehow
17/// crash earlier than that.
18pub var zig_argv0: []const u8 = "zig";
19
20fn handleSegfaultImpl(addr: ?usize, name: []const u8, opt_ctx: ?*std.debug.ThreadContext) noreturn {
21 @branchHint(.cold);
22 dumpCrashContext() catch {};
23 std.debug.defaultHandleSegfault(addr, name, opt_ctx);
24}
25fn panicImpl(msg: []const u8, first_trace_addr: ?usize) noreturn {
26 @branchHint(.cold);
27 dumpCrashContext() catch {};
28 std.debug.defaultPanic(msg, first_trace_addr orelse @returnAddress());
3229}
3330
3431pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct {
......@@ -38,63 +35,96 @@ pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct {
3835 body: []const Zir.Inst.Index,
3936 body_index: usize,
4037
41 pub fn push(self: *@This()) void {
42 const head = &zir_state;
43 debug.assert(self.parent == null);
44 self.parent = head.*;
45 head.* = self;
46 }
38 threadlocal var current: ?*AnalyzeBody = null;
4739
48 pub fn pop(self: *@This()) void {
49 const head = &zir_state;
50 const old = head.*.?;
51 debug.assert(old == self);
52 head.* = old.parent;
40 pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void {
41 ab.body_index = index;
5342 }
5443
55 pub fn setBodyIndex(self: *@This(), index: usize) void {
56 self.body_index = index;
44 pub fn push(ab: *AnalyzeBody, sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) void {
45 ab.* = .{
46 .parent = current,
47 .sema = sema,
48 .block = block,
49 .body = body,
50 .body_index = 0,
51 };
52 current = ab;
53 }
54 pub fn pop(ab: *AnalyzeBody) void {
55 std.debug.assert(current.? == ab); // `Sema.analyzeBodyInner` did not match push/pop calls
56 current = ab.parent;
5757 }
5858} else struct {
59 pub inline fn push(_: @This()) void {}
60 pub inline fn pop(_: @This()) void {}
59 // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls.
60 pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {}
61 pub inline fn pop(_: AnalyzeBody) void {}
6162 pub inline fn setBodyIndex(_: @This(), _: usize) void {}
6263};
6364
64threadlocal var zir_state: ?*AnalyzeBody = if (build_options.enable_debug_extensions) null else @compileError("Cannot use zir_state without debug extensions.");
65pub const CodegenFunc = if (build_options.enable_debug_extensions) struct {
66 zcu: *const Zcu,
67 func_index: InternPool.Index,
68 threadlocal var current: ?CodegenFunc = null;
69 pub fn start(zcu: *const Zcu, func_index: InternPool.Index) void {
70 std.debug.assert(current == null);
71 current = .{ .zcu = zcu, .func_index = func_index };
72 }
73 pub fn stop(func_index: InternPool.Index) void {
74 std.debug.assert(current.?.func_index == func_index);
75 current = null;
76 }
77} else struct {
78 // Dummy implementation
79 pub fn start(_: *const Zcu, _: InternPool.Index) void {}
80 pub fn stop(_: InternPool.Index) void {}
81};
6582
66pub fn prepAnalyzeBody(sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) AnalyzeBody {
67 return if (build_options.enable_debug_extensions) .{
68 .parent = null,
69 .sema = sema,
70 .block = block,
71 .body = body,
72 .body_index = 0,
73 } else .{};
74}
83fn dumpCrashContext() Io.Writer.Error!void {
84 const S = struct {
85 /// In the case of recursive panics or segfaults, don't print the context for a second time.
86 threadlocal var already_dumped = false;
87 /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator,
88 /// but that shouldn't be necessary---it's already only used in one place.
89 threadlocal var crash_heap: [64 * 1024]u8 = undefined;
90 };
91 if (S.already_dumped) return;
92 S.already_dumped = true;
93
94 // TODO: this does mean that a different thread could grab the stderr mutex between the context
95 // and the actual panic printing, which would be quite confusing.
96 const stderr = std.debug.lockStderrWriter(&.{});
97 defer std.debug.unlockStderrWriter();
7598
76fn dumpStatusReport() !void {
77 const anal = zir_state orelse return;
78 // Note: We have the panic mutex here, so we can safely use the global crash heap.
79 var fba = std.heap.FixedBufferAllocator.init(&crash_heap);
80 const allocator = fba.allocator();
99 try stderr.writeAll("Compiler crash context:\n");
81100
82 var stderr_fw = std.fs.File.stderr().writer(&.{});
83 const stderr = &stderr_fw.interface;
101 if (CodegenFunc.current) |*cg| {
102 const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav;
103 const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn;
104 try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
105 } else if (AnalyzeBody.current) |anal| {
106 try dumpCrashContextSema(anal, stderr, &S.crash_heap);
107 } else {
108 try stderr.writeAll("(no context)\n\n");
109 }
110}
111fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void {
84112 const block: *Sema.Block = anal.block;
85113 const zcu = anal.sema.pt.zcu;
114 const comp = zcu.comp;
115
116 var fba: std.heap.FixedBufferAllocator = .init(crash_heap);
86117
87118 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse {
88119 const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool));
89 try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(zcu.comp)});
120 try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(comp)});
90121 return;
91122 };
92123
93 try stderr.writeAll("Analyzing ");
94 try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(zcu.comp)});
124 try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)});
95125
96126 print_zir.renderInstructionContext(
97 allocator,
127 fba.allocator(),
98128 anal.body,
99129 anal.body_index,
100130 file,
......@@ -107,16 +137,16 @@ fn dumpStatusReport() !void {
107137 };
108138 try stderr.print(
109139 \\ For full context, use the command
110 \\ zig ast-check -t {f}
140 \\ {s} ast-check -t {f}
111141 \\
112142 \\
113 , .{file.path.fmt(zcu.comp)});
143 , .{ zig_argv0, file.path.fmt(comp) });
114144
115145 var parent = anal.parent;
116146 while (parent) |curr| {
117147 fba.reset();
118148 const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool));
119 try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(zcu.comp)});
149 try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(comp)});
120150 _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse {
121151 try stderr.writeAll(" > [lost instruction; this should not happen]\n");
122152 parent = curr.parent;
......@@ -124,7 +154,7 @@ fn dumpStatusReport() !void {
124154 };
125155 try stderr.writeAll(" > ");
126156 print_zir.renderSingleInstruction(
127 allocator,
157 fba.allocator(),
128158 curr.body[curr.body_index],
129159 cur_block_file,
130160 cur_block_src_base_node,
......@@ -142,398 +172,14 @@ fn dumpStatusReport() !void {
142172 try stderr.writeByte('\n');
143173}
144174
145var crash_heap: [16 * 4096]u8 = undefined;
146
147pub fn compilerPanic(msg: []const u8, maybe_ret_addr: ?usize) noreturn {
148 @branchHint(.cold);
149 PanicSwitch.preDispatch();
150 const ret_addr = maybe_ret_addr orelse @returnAddress();
151 const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } };
152 PanicSwitch.dispatch(@errorReturnTrace(), stack_ctx, msg);
153}
154
155/// Attaches a global SIGSEGV handler
156pub fn attachSegfaultHandler() void {
157 if (!debug.have_segfault_handling_support) {
158 @compileError("segfault handler not supported for this target");
159 }
160 if (native_os == .windows) {
161 _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
162 return;
163 }
164 const act: posix.Sigaction = .{
165 .handler = .{ .sigaction = handleSegfaultPosix },
166 .mask = posix.sigemptyset(),
167 .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND),
168 };
169 debug.updateSegfaultHandler(&act);
170}
171
172fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
173 // TODO: use alarm() here to prevent infinite loops
174 PanicSwitch.preDispatch();
175
176 const addr = switch (native_os) {
177 .linux => @intFromPtr(info.fields.sigfault.addr),
178 .freebsd, .macos => @intFromPtr(info.addr),
179 .netbsd => @intFromPtr(info.info.reason.fault.addr),
180 .openbsd => @intFromPtr(info.data.fault.addr),
181 .solaris, .illumos => @intFromPtr(info.reason.fault.addr),
182 else => @compileError("TODO implement handleSegfaultPosix for new POSIX OS"),
183 };
184
185 var err_buffer: [128]u8 = undefined;
186 const error_msg = switch (sig) {
187 posix.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault",
188 posix.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction",
189 posix.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error",
190 else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error",
191 };
192
193 const stack_ctx: StackContext = switch (builtin.cpu.arch) {
194 .x86,
195 .x86_64,
196 .arm,
197 .aarch64,
198 => StackContext{ .exception = @ptrCast(@alignCast(ctx_ptr)) },
199 else => .not_supported,
200 };
201
202 PanicSwitch.dispatch(null, stack_ctx, error_msg);
203}
204
205const WindowsSegfaultMessage = union(enum) {
206 literal: []const u8,
207 segfault: void,
208 illegal_instruction: void,
209};
210
211fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {
212 switch (info.ExceptionRecord.ExceptionCode) {
213 windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }),
214 windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault),
215 windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction),
216 windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }),
217 else => return windows.EXCEPTION_CONTINUE_SEARCH,
218 }
219}
220
221fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn {
222 PanicSwitch.preDispatch();
223
224 const stack_ctx = if (@hasDecl(windows, "CONTEXT"))
225 StackContext{ .exception = info.ContextRecord }
226 else ctx: {
227 const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress);
228 break :ctx StackContext{ .current = .{ .ret_addr = addr } };
229 };
230
231 switch (msg) {
232 .literal => |err| PanicSwitch.dispatch(null, stack_ctx, err),
233 .segfault => {
234 const format_item = "Segmentation fault at address 0x{x}";
235 var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large
236 const to_print = std.fmt.bufPrint(&buf, format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable;
237 PanicSwitch.dispatch(null, stack_ctx, to_print);
238 },
239 .illegal_instruction => {
240 const ip: ?usize = switch (stack_ctx) {
241 .exception => |ex| ex.getRegs().ip,
242 .current => |cur| cur.ret_addr,
243 .not_supported => null,
244 };
245
246 if (ip) |addr| {
247 const format_item = "Illegal instruction at address 0x{x}";
248 var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large
249 const to_print = std.fmt.bufPrint(&buf, format_item, .{addr}) catch unreachable;
250 PanicSwitch.dispatch(null, stack_ctx, to_print);
251 } else {
252 PanicSwitch.dispatch(null, stack_ctx, "Illegal Instruction");
253 }
254 },
255 }
256}
257
258const StackContext = union(enum) {
259 current: struct {
260 ret_addr: ?usize,
261 },
262 exception: *debug.ThreadContext,
263 not_supported: void,
264
265 pub fn dumpStackTrace(ctx: @This()) void {
266 switch (ctx) {
267 .current => |ct| {
268 debug.dumpCurrentStackTrace(ct.ret_addr);
269 },
270 .exception => |context| {
271 var stderr_fw = std.fs.File.stderr().writer(&.{});
272 const stderr = &stderr_fw.interface;
273 debug.dumpStackTraceFromBase(context, stderr);
274 },
275 .not_supported => {
276 std.fs.File.stderr().writeAll("Stack trace not supported on this platform.\n") catch {};
277 },
278 }
279 }
280};
281
282const PanicSwitch = struct {
283 const RecoverStage = enum {
284 initialize,
285 report_stack,
286 release_mutex,
287 release_ref_count,
288 abort,
289 silent_abort,
290 };
291
292 const RecoverVerbosity = enum {
293 message_and_stack,
294 message_only,
295 silent,
296 };
297
298 const PanicState = struct {
299 recover_stage: RecoverStage = .initialize,
300 recover_verbosity: RecoverVerbosity = .message_and_stack,
301 panic_ctx: StackContext = undefined,
302 panic_trace: ?*const std.builtin.StackTrace = null,
303 awaiting_dispatch: bool = false,
304 };
305
306 /// Counter for the number of threads currently panicking.
307 /// Updated atomically before taking the panic_mutex.
308 /// In recoverable cases, the program will not abort
309 /// until all panicking threads have dumped their traces.
310 var panicking = std.atomic.Value(u8).init(0);
311
312 /// Tracks the state of the current panic. If the code within the
313 /// panic triggers a secondary panic, this allows us to recover.
314 threadlocal var panic_state_raw: PanicState = .{};
315
316 /// The segfault handlers above need to do some work before they can dispatch
317 /// this switch. Calling preDispatch() first makes that work fault tolerant.
318 pub fn preDispatch() void {
319 // TODO: We want segfaults to trigger the panic recursively here,
320 // but if there is a segfault accessing this TLS slot it will cause an
321 // infinite loop. We should use `alarm()` to prevent the infinite
322 // loop and maybe also use a non-thread-local global to detect if
323 // it's happening and print a message.
324 var panic_state: *volatile PanicState = &panic_state_raw;
325 if (panic_state.awaiting_dispatch) {
326 dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack");
327 }
328 panic_state.awaiting_dispatch = true;
329 }
330
331 /// This is the entry point to a panic-tolerant panic handler.
332 /// preDispatch() *MUST* be called exactly once before calling this.
333 /// A threadlocal "recover_stage" is updated throughout the process.
334 /// If a panic happens during the panic, the recover_stage will be
335 /// used to select a recover* function to call to resume the panic.
336 /// The recover_verbosity field is used to handle panics while reporting
337 /// panics within panics. If the panic handler triggers a panic, it will
338 /// attempt to log an additional stack trace for the secondary panic. If
339 /// that panics, it will fall back to just logging the panic message. If
340 /// it can't even do that witout panicing, it will recover without logging
341 /// anything about the internal panic. Depending on the state, "recover"
342 /// here may just mean "call abort".
343 pub fn dispatch(
344 trace: ?*const std.builtin.StackTrace,
345 stack_ctx: StackContext,
346 msg: []const u8,
347 ) noreturn {
348 var panic_state: *volatile PanicState = &panic_state_raw;
349 debug.assert(panic_state.awaiting_dispatch);
350 panic_state.awaiting_dispatch = false;
351 nosuspend switch (panic_state.recover_stage) {
352 .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }),
353 .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }),
354 .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }),
355 .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }),
356 .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }),
357 .silent_abort => goTo(abort, .{}),
358 };
359 }
360
361 noinline fn initPanic(
362 state: *volatile PanicState,
363 trace: ?*const std.builtin.StackTrace,
364 stack: StackContext,
365 msg: []const u8,
366 ) noreturn {
367 // use a temporary so there's only one volatile store
368 const new_state = PanicState{
369 .recover_stage = .abort,
370 .panic_ctx = stack,
371 .panic_trace = trace,
372 };
373 state.* = new_state;
374
375 _ = panicking.fetchAdd(1, .seq_cst);
376
377 state.recover_stage = .release_ref_count;
378
379 std.debug.lockStdErr();
380
381 state.recover_stage = .release_mutex;
382
383 var stderr_fw = std.fs.File.stderr().writer(&.{});
384 const stderr = &stderr_fw.interface;
385 if (builtin.single_threaded) {
386 stderr.print("panic: ", .{}) catch goTo(releaseMutex, .{state});
387 } else {
388 const current_thread_id = std.Thread.getCurrentId();
389 stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state});
390 }
391 stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state});
392
393 state.recover_stage = .report_stack;
394
395 dumpStatusReport() catch |err| {
396 stderr.print("\nIntercepted error.{} while dumping current state. Continuing...\n", .{err}) catch {};
397 };
398
399 goTo(reportStack, .{state});
400 }
401
402 noinline fn recoverReportStack(
403 state: *volatile PanicState,
404 trace: ?*const std.builtin.StackTrace,
405 stack: StackContext,
406 msg: []const u8,
407 ) noreturn {
408 recover(state, trace, stack, msg);
409
410 state.recover_stage = .release_mutex;
411 var stderr_fw = std.fs.File.stderr().writer(&.{});
412 const stderr = &stderr_fw.interface;
413 stderr.writeAll("\nOriginal Error:\n") catch {};
414 goTo(reportStack, .{state});
415 }
416
417 noinline fn reportStack(state: *volatile PanicState) noreturn {
418 state.recover_stage = .release_mutex;
419
420 if (state.panic_trace) |t| {
421 debug.dumpStackTrace(t.*);
422 }
423 state.panic_ctx.dumpStackTrace();
424
425 goTo(releaseMutex, .{state});
426 }
427
428 noinline fn recoverReleaseMutex(
429 state: *volatile PanicState,
430 trace: ?*const std.builtin.StackTrace,
431 stack: StackContext,
432 msg: []const u8,
433 ) noreturn {
434 recover(state, trace, stack, msg);
435 goTo(releaseMutex, .{state});
436 }
437
438 noinline fn releaseMutex(state: *volatile PanicState) noreturn {
439 state.recover_stage = .abort;
440
441 std.debug.unlockStdErr();
442
443 goTo(releaseRefCount, .{state});
444 }
445
446 noinline fn recoverReleaseRefCount(
447 state: *volatile PanicState,
448 trace: ?*const std.builtin.StackTrace,
449 stack: StackContext,
450 msg: []const u8,
451 ) noreturn {
452 recover(state, trace, stack, msg);
453 goTo(releaseRefCount, .{state});
454 }
455
456 noinline fn releaseRefCount(state: *volatile PanicState) noreturn {
457 state.recover_stage = .abort;
458
459 if (panicking.fetchSub(1, .seq_cst) != 1) {
460 // Another thread is panicking, wait for the last one to finish
461 // and call abort()
462
463 // Sleep forever without hammering the CPU
464 var futex = std.atomic.Value(u32).init(0);
465 while (true) std.Thread.Futex.wait(&futex, 0);
466
467 // This should be unreachable, recurse into recoverAbort.
468 @panic("event.wait() returned");
469 }
470
471 goTo(abort, .{});
472 }
473
474 noinline fn recoverAbort(
475 state: *volatile PanicState,
476 trace: ?*const std.builtin.StackTrace,
477 stack: StackContext,
478 msg: []const u8,
479 ) noreturn {
480 recover(state, trace, stack, msg);
481
482 state.recover_stage = .silent_abort;
483 var stderr_fw = std.fs.File.stderr().writer(&.{});
484 const stderr = &stderr_fw.interface;
485 stderr.writeAll("Aborting...\n") catch {};
486 goTo(abort, .{});
487 }
488
489 noinline fn abort() noreturn {
490 std.process.abort();
491 }
492
493 inline fn goTo(comptime func: anytype, args: anytype) noreturn {
494 // TODO: Tailcall is broken right now, but eventually this should be used
495 // to avoid blowing up the stack. It's ok for now though, there are no
496 // cycles in the state machine so the max stack usage is bounded.
497 //@call(.always_tail, func, args);
498 @call(.auto, func, args);
499 }
500
501 fn recover(
502 state: *volatile PanicState,
503 trace: ?*const std.builtin.StackTrace,
504 stack: StackContext,
505 msg: []const u8,
506 ) void {
507 switch (state.recover_verbosity) {
508 .message_and_stack => {
509 // lower the verbosity, and restore it at the end if we don't panic.
510 state.recover_verbosity = .message_only;
511
512 var stderr_fw = std.fs.File.stderr().writer(&.{});
513 const stderr = &stderr_fw.interface;
514 stderr.writeAll("\nPanicked during a panic: ") catch {};
515 stderr.writeAll(msg) catch {};
516 stderr.writeAll("\nInner panic stack:\n") catch {};
517 if (trace) |t| {
518 debug.dumpStackTrace(t.*);
519 }
520 stack.dumpStackTrace();
521
522 state.recover_verbosity = .message_and_stack;
523 },
524 .message_only => {
525 state.recover_verbosity = .silent;
175const std = @import("std");
176const Io = std.Io;
177const Zir = std.zig.Zir;
526178
527 var stderr_fw = std.fs.File.stderr().writer(&.{});
528 const stderr = &stderr_fw.interface;
529 stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {};
530 stderr.writeAll(msg) catch {};
531 stderr.writeByte('\n') catch {};
179const Sema = @import("Sema.zig");
180const Zcu = @import("Zcu.zig");
181const InternPool = @import("InternPool.zig");
182const dev = @import("dev.zig");
183const print_zir = @import("print_zir.zig");
532184
533 // If we succeed, restore all the way to dumping the stack.
534 state.recover_verbosity = .message_and_stack;
535 },
536 .silent => {},
537 }
538 }
539};
185const build_options = @import("build_options");
src/main.zig+3-3
......@@ -43,7 +43,6 @@ const thread_stack_size = 60 << 20;
4343pub const std_options: std.Options = .{
4444 .wasiCwd = wasi_cwd,
4545 .logFn = log,
46 .enable_segfault_handler = false,
4746
4847 .log_level = switch (builtin.mode) {
4948 .Debug => .debug,
......@@ -53,6 +52,7 @@ pub const std_options: std.Options = .{
5352};
5453
5554pub const panic = crash_report.panic;
55pub const debug = crash_report.debug;
5656
5757var wasi_preopens: fs.wasi.Preopens = undefined;
5858pub fn wasi_cwd() std.os.wasi.fd_t {
......@@ -165,8 +165,6 @@ var debug_allocator: std.heap.DebugAllocator(.{
165165}) = .init;
166166
167167pub fn main() anyerror!void {
168 crash_report.initialize();
169
170168 const gpa, const is_debug = gpa: {
171169 if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true };
172170 if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false };
......@@ -192,6 +190,8 @@ pub fn main() anyerror!void {
192190
193191 const args = try process.argsAlloc(arena);
194192
193 if (args.len > 0) crash_report.zig_argv0 = args[0];
194
195195 if (tracy.enable_allocation) {
196196 var gpa_tracy = tracy.tracyAllocator(gpa);
197197 return mainArgs(gpa_tracy.allocator(), arena, args);