| ... | ... | @@ -1,34 +1,31 @@ |
| 1 | | const std = @import("std"); |
| 2 | | const builtin = @import("builtin"); |
| 3 | | const build_options = @import("build_options"); |
| 4 | | const debug = std.debug; |
| 5 | | const print_zir = @import("print_zir.zig"); |
| 6 | | const windows = std.os.windows; |
| 7 | | const posix = std.posix; |
| 8 | | const native_os = builtin.os.tag; |
| 9 | | |
| 10 | | const Zcu = @import("Zcu.zig"); |
| 11 | | const Sema = @import("Sema.zig"); |
| 12 | | const InternPool = @import("InternPool.zig"); |
| 13 | | const Zir = std.zig.Zir; |
| 14 | | const Decl = Zcu.Decl; |
| 15 | | const 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. |
| 20 | | pub const panic = if (build_options.enable_debug_extensions) |
| 21 | | std.debug.FullPanic(compilerPanic) |
| 22 | | else 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")`. |
| 3 | pub const panic = if (dev.env == .bootstrap) |
| 23 | 4 | std.debug.simple_panic |
| 24 | 5 | else |
| 25 | | std.debug.FullPanic(std.debug.defaultPanic); |
| 6 | std.debug.FullPanic(panicImpl); |
| 26 | 7 | |
| 27 | | /// Install signal handlers to identify crashes and report diagnostics. |
| 28 | | pub 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")`. |
| 11 | pub 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. |
| 18 | pub var zig_argv0: []const u8 = "zig"; |
| 19 | |
| 20 | fn 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 | } |
| 25 | fn 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()); |
| 32 | 29 | } |
| 33 | 30 | |
| 34 | 31 | pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { |
| ... | ... | @@ -38,63 +35,96 @@ pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { |
| 38 | 35 | body: []const Zir.Inst.Index, |
| 39 | 36 | body_index: usize, |
| 40 | 37 | |
| 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; |
| 47 | 39 | |
| 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; |
| 53 | 42 | } |
| 54 | 43 | |
| 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; |
| 57 | 57 | } |
| 58 | 58 | } 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 {} |
| 61 | 62 | pub inline fn setBodyIndex(_: @This(), _: usize) void {} |
| 62 | 63 | }; |
| 63 | 64 | |
| 64 | | threadlocal var zir_state: ?*AnalyzeBody = if (build_options.enable_debug_extensions) null else @compileError("Cannot use zir_state without debug extensions."); |
| 65 | pub 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 | }; |
| 65 | 82 | |
| 66 | | pub 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 | | } |
| 83 | fn 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(); |
| 75 | 98 | |
| 76 | | fn 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"); |
| 81 | 100 | |
| 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 | } |
| 111 | fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void { |
| 84 | 112 | const block: *Sema.Block = anal.block; |
| 85 | 113 | const zcu = anal.sema.pt.zcu; |
| 114 | const comp = zcu.comp; |
| 115 | |
| 116 | var fba: std.heap.FixedBufferAllocator = .init(crash_heap); |
| 86 | 117 | |
| 87 | 118 | const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse { |
| 88 | 119 | 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)}); |
| 90 | 121 | return; |
| 91 | 122 | }; |
| 92 | 123 | |
| 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)}); |
| 95 | 125 | |
| 96 | 126 | print_zir.renderInstructionContext( |
| 97 | | allocator, |
| 127 | fba.allocator(), |
| 98 | 128 | anal.body, |
| 99 | 129 | anal.body_index, |
| 100 | 130 | file, |
| ... | ... | @@ -107,16 +137,16 @@ fn dumpStatusReport() !void { |
| 107 | 137 | }; |
| 108 | 138 | try stderr.print( |
| 109 | 139 | \\ For full context, use the command |
| 110 | | \\ zig ast-check -t {f} |
| 140 | \\ {s} ast-check -t {f} |
| 111 | 141 | \\ |
| 112 | 142 | \\ |
| 113 | | , .{file.path.fmt(zcu.comp)}); |
| 143 | , .{ zig_argv0, file.path.fmt(comp) }); |
| 114 | 144 | |
| 115 | 145 | var parent = anal.parent; |
| 116 | 146 | while (parent) |curr| { |
| 117 | 147 | fba.reset(); |
| 118 | 148 | 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)}); |
| 120 | 150 | _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse { |
| 121 | 151 | try stderr.writeAll(" > [lost instruction; this should not happen]\n"); |
| 122 | 152 | parent = curr.parent; |
| ... | ... | @@ -124,7 +154,7 @@ fn dumpStatusReport() !void { |
| 124 | 154 | }; |
| 125 | 155 | try stderr.writeAll(" > "); |
| 126 | 156 | print_zir.renderSingleInstruction( |
| 127 | | allocator, |
| 157 | fba.allocator(), |
| 128 | 158 | curr.body[curr.body_index], |
| 129 | 159 | cur_block_file, |
| 130 | 160 | cur_block_src_base_node, |
| ... | ... | @@ -142,398 +172,14 @@ fn dumpStatusReport() !void { |
| 142 | 172 | try stderr.writeByte('\n'); |
| 143 | 173 | } |
| 144 | 174 | |
| 145 | | var crash_heap: [16 * 4096]u8 = undefined; |
| 146 | | |
| 147 | | pub 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 |
| 156 | | pub 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 | | |
| 172 | | fn 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 | | |
| 205 | | const WindowsSegfaultMessage = union(enum) { |
| 206 | | literal: []const u8, |
| 207 | | segfault: void, |
| 208 | | illegal_instruction: void, |
| 209 | | }; |
| 210 | | |
| 211 | | fn 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 | | |
| 221 | | fn 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 | | |
| 258 | | const 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 | | |
| 282 | | const 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; |
| 175 | const std = @import("std"); |
| 176 | const Io = std.Io; |
| 177 | const Zir = std.zig.Zir; |
| 526 | 178 | |
| 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 {}; |
| 179 | const Sema = @import("Sema.zig"); |
| 180 | const Zcu = @import("Zcu.zig"); |
| 181 | const InternPool = @import("InternPool.zig"); |
| 182 | const dev = @import("dev.zig"); |
| 183 | const print_zir = @import("print_zir.zig"); |
| 532 | 184 | |
| 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 | | }; |
| 185 | const build_options = @import("build_options"); |