From 251f54d1d7d0f871f551c54d77f1a8238a041983 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Thu, 12 Feb 2026 12:59:22 -0500 Subject: [PATCH] crash_report: finish reverting panic changes --- src/crash_report.zig | 57 +++++++++++++++++++++++++++----------------- src/main.zig | 9 +++---- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/crash_report.zig b/src/crash_report.zig index 41d56e8aaf..1723ad24a0 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -1,14 +1,31 @@ +/// We override the panic implementation to our own one, so we can print our own information before +/// calling the default panic handler. This declaration must be re-exposed from `@import("root")`. +pub const panic = std.debug.FullPanic(panicImpl); + +/// We let std install its segfault handler, but we override the target-agnostic handler it calls, +/// so we can print our own information before calling the default segfault logic. This declaration +/// must be re-exposed from `@import("root")`. +pub const debug = struct { + pub const handleSegfault = handleSegfaultImpl; +}; + /// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command. /// Set by `main` as soon as arguments are known. The value here is a default in case we somehow /// crash earlier than that. pub var zig_argv0: []const u8 = "zig"; -const enabled = switch (build_options.io_mode) { - .threaded => build_options.enable_debug_extensions, - .evented => false, // would use threadlocals in a way incompatible with evented -}; +fn handleSegfaultImpl(addr: ?usize, name: []const u8, opt_ctx: ?std.debug.CpuContextPtr) noreturn { + @branchHint(.cold); + dumpCrashContext() catch {}; + std.debug.defaultHandleSegfault(addr, name, opt_ctx); +} +fn panicImpl(msg: []const u8, first_trace_addr: ?usize) noreturn { + @branchHint(.cold); + dumpCrashContext() catch {}; + std.debug.defaultPanic(msg, first_trace_addr orelse @returnAddress()); +} -pub const AnalyzeBody = if (enabled) struct { +pub const AnalyzeBody = struct { parent: ?*AnalyzeBody, sema: *Sema, block: *Sema.Block, @@ -35,15 +52,9 @@ pub const AnalyzeBody = if (enabled) struct { std.debug.assert(current.? == ab); // `Sema.analyzeBodyInner` did not match push/pop calls current = ab.parent; } -} else struct { - const current: ?noreturn = null; - // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls. - pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {} - pub inline fn pop(_: AnalyzeBody) void {} - pub inline fn setBodyIndex(_: @This(), _: usize) void {} }; -pub const CodegenFunc = if (enabled) struct { +pub const CodegenFunc = struct { zcu: *const Zcu, func_index: InternPool.Index, threadlocal var current: ?CodegenFunc = null; @@ -55,21 +66,25 @@ pub const CodegenFunc = if (enabled) struct { std.debug.assert(current.?.func_index == func_index); current = null; } -} else struct { - const current: ?noreturn = null; - // Dummy implementation - pub fn start(_: *const Zcu, _: InternPool.Index) void {} - pub fn stop(_: InternPool.Index) void {} }; -pub fn dumpCrashContext(terminal: Io.Terminal) Io.Writer.Error!void { +fn dumpCrashContext() Io.Writer.Error!void { const S = struct { + /// In the case of recursive panics or segfaults, don't print the context for a second time. + threadlocal var already_dumped = false; /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator, /// but that shouldn't be necessary---it's already only used in one place. - var crash_heap: [64 * 1024]u8 = undefined; + threadlocal var crash_heap: [64 * 1024]u8 = undefined; }; + if (S.already_dumped) return; + S.already_dumped = true; + + // TODO: this does mean that a different thread could grab the stderr mutex between the context + // and the actual panic printing, which would be quite confusing. + const stderr = std.debug.lockStderr(&.{}); + defer std.debug.unlockStderr(); + const w = &stderr.file_writer.interface; - const w = terminal.writer; try w.writeAll("Compiler crash context:\n"); if (CodegenFunc.current) |*cg| { @@ -155,5 +170,3 @@ const Zcu = @import("Zcu.zig"); const InternPool = @import("InternPool.zig"); const dev = @import("dev.zig"); const print_zir = @import("print_zir.zig"); - -const build_options = @import("build_options"); diff --git a/src/main.zig b/src/main.zig index 82a1fcf050..89a24559ed 100644 --- a/src/main.zig +++ b/src/main.zig @@ -52,11 +52,12 @@ pub const std_options: std.Options = .{ }; pub const std_options_cwd = if (native_os == .wasi) wasi_cwd else null; -pub const debug = struct { - pub fn printCrashContext(terminal: Io.Terminal) void { - crash_report.dumpCrashContext(terminal) catch {}; - } +const crash_report_enabled = switch (build_options.io_mode) { + .threaded => build_options.enable_debug_extensions, + .evented => false, // would use threadlocals in a way incompatible with evented }; +pub const panic = if (crash_report_enabled) crash_report.panic else std.debug.FullPanic(std.debug.defaultPanic); +pub const debug = if (crash_report_enabled) crash_report.debug else struct {}; var preopens: std.process.Preopens = .empty; pub fn wasi_cwd() Io.Dir {