| 1 | pub 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")`. |
| 8 | pub 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")`. |
| 13 | pub 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. |
| 20 | pub var zig_argv0: []const u8 = "zig"; |
| 21 | |
| 22 | fn 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 | } |
| 27 | fn 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 | |
| 33 | pub 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 | |
| 68 | pub 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 | |
| 87 | pub 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 | |
| 106 | fn 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 | } |
| 146 | fn 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 | |
| 210 | const std = @import("std"); |
| 211 | const Io = std.Io; |
| 212 | const Zir = std.zig.Zir; |
| 213 | |
| 214 | const Sema = @import("Sema.zig"); |
| 215 | const Zcu = @import("Zcu.zig"); |
| 216 | const link = @import("link.zig"); |
| 217 | const InternPool = @import("InternPool.zig"); |
| 218 | const print_zir = @import("print_zir.zig"); |
| 219 | |
| 220 | const build_options = @import("build_options"); |