master
  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)
  4    std.debug.simple_panic
  5else
  6    std.debug.FullPanic(panicImpl);
  7
  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.CpuContextPtr) 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());
 29}
 30
 31pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct {
 32    parent: ?*AnalyzeBody,
 33    sema: *Sema,
 34    block: *Sema.Block,
 35    body: []const Zir.Inst.Index,
 36    body_index: usize,
 37
 38    threadlocal var current: ?*AnalyzeBody = null;
 39
 40    pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void {
 41        ab.body_index = index;
 42    }
 43
 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    }
 58} else struct {
 59    const current: ?noreturn = null;
 60    // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls.
 61    pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {}
 62    pub inline fn pop(_: AnalyzeBody) void {}
 63    pub inline fn setBodyIndex(_: @This(), _: usize) void {}
 64};
 65
 66pub const CodegenFunc = if (build_options.enable_debug_extensions) struct {
 67    zcu: *const Zcu,
 68    func_index: InternPool.Index,
 69    threadlocal var current: ?CodegenFunc = null;
 70    pub fn start(zcu: *const Zcu, func_index: InternPool.Index) void {
 71        std.debug.assert(current == null);
 72        current = .{ .zcu = zcu, .func_index = func_index };
 73    }
 74    pub fn stop(func_index: InternPool.Index) void {
 75        std.debug.assert(current.?.func_index == func_index);
 76        current = null;
 77    }
 78} else struct {
 79    const current: ?noreturn = null;
 80    // Dummy implementation
 81    pub fn start(_: *const Zcu, _: InternPool.Index) void {}
 82    pub fn stop(_: InternPool.Index) void {}
 83};
 84
 85fn dumpCrashContext() Io.Writer.Error!void {
 86    const S = struct {
 87        /// In the case of recursive panics or segfaults, don't print the context for a second time.
 88        threadlocal var already_dumped = false;
 89        /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator,
 90        /// but that shouldn't be necessary---it's already only used in one place.
 91        threadlocal var crash_heap: [64 * 1024]u8 = undefined;
 92    };
 93    if (S.already_dumped) return;
 94    S.already_dumped = true;
 95
 96    // TODO: this does mean that a different thread could grab the stderr mutex between the context
 97    // and the actual panic printing, which would be quite confusing.
 98    const stderr, _ = std.debug.lockStderrWriter(&.{});
 99    defer std.debug.unlockStderrWriter();
100
101    try stderr.writeAll("Compiler crash context:\n");
102
103    if (CodegenFunc.current) |*cg| {
104        const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav;
105        const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn;
106        try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
107    } else if (AnalyzeBody.current) |anal| {
108        try dumpCrashContextSema(anal, stderr, &S.crash_heap);
109    } else {
110        try stderr.writeAll("(no context)\n\n");
111    }
112}
113fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void {
114    const block: *Sema.Block = anal.block;
115    const zcu = anal.sema.pt.zcu;
116    const comp = zcu.comp;
117
118    var fba: std.heap.FixedBufferAllocator = .init(crash_heap);
119
120    const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse {
121        const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool));
122        try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(comp)});
123        return;
124    };
125
126    try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)});
127
128    print_zir.renderInstructionContext(
129        fba.allocator(),
130        anal.body,
131        anal.body_index,
132        file,
133        src_base_node,
134        6, // indent
135        stderr,
136    ) catch |err| switch (err) {
137        error.OutOfMemory => try stderr.writeAll("  <out of memory dumping zir>\n"),
138        else => |e| return e,
139    };
140    try stderr.print(
141        \\    For full context, use the command
142        \\      {s} ast-check -t {f}
143        \\
144        \\
145    , .{ zig_argv0, file.path.fmt(comp) });
146
147    var parent = anal.parent;
148    while (parent) |curr| {
149        fba.reset();
150        const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool));
151        try stderr.print("  in {f}\n", .{cur_block_file.path.fmt(comp)});
152        _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse {
153            try stderr.writeAll("    > [lost instruction; this should not happen]\n");
154            parent = curr.parent;
155            continue;
156        };
157        try stderr.writeAll("    > ");
158        print_zir.renderSingleInstruction(
159            fba.allocator(),
160            curr.body[curr.body_index],
161            cur_block_file,
162            cur_block_src_base_node,
163            6, // indent
164            stderr,
165        ) catch |err| switch (err) {
166            error.OutOfMemory => try stderr.writeAll("  <out of memory dumping zir>\n"),
167            else => |e| return e,
168        };
169        try stderr.writeAll("\n");
170
171        parent = curr.parent;
172    }
173
174    try stderr.writeByte('\n');
175}
176
177const std = @import("std");
178const Io = std.Io;
179const Zir = std.zig.Zir;
180
181const Sema = @import("Sema.zig");
182const Zcu = @import("Zcu.zig");
183const InternPool = @import("InternPool.zig");
184const dev = @import("dev.zig");
185const print_zir = @import("print_zir.zig");
186
187const build_options = @import("build_options");