authorgravatar for spexguy070@gmail.comMartin Wickham <spexguy070@gmail.com> 2021-09-29 18:37:12-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-30 17:39:01-04:00
logf87156e33c688effcf00b8fa9c2542391423ee78
tree6b209218f51ca1a9e70bf246e364be7534cfd3fa
parent2ed9288246821c39ae75fa21998a53b34e713cd4

Add a panic handler to give better errors for crashes in sema


5 files changed, 893 insertions(+), 219 deletions(-)

lib/std/Thread/Mutex.zig+97-80
...@@ -33,17 +33,29 @@ const testing = std.testing;...@@ -33,17 +33,29 @@ const testing = std.testing;
33const StaticResetEvent = std.thread.StaticResetEvent;33const StaticResetEvent = std.thread.StaticResetEvent;
3434
35/// Try to acquire the mutex without blocking. Returns `null` if the mutex is35/// Try to acquire the mutex without blocking. Returns `null` if the mutex is
36/// unavailable. Otherwise returns `Held`. Call `release` on `Held`.36/// unavailable. Otherwise returns `Held`. Call `release` on `Held`, or use
37pub fn tryAcquire(m: *Mutex) ?Impl.Held {37/// releaseDirect().
38pub fn tryAcquire(m: *Mutex) ?Held {
38 return m.impl.tryAcquire();39 return m.impl.tryAcquire();
39}40}
4041
41/// Acquire the mutex. Deadlocks if the mutex is already42/// Acquire the mutex. Deadlocks if the mutex is already
42/// held by the calling thread.43/// held by the calling thread.
43pub fn acquire(m: *Mutex) Impl.Held {44pub fn acquire(m: *Mutex) Held {
44 return m.impl.acquire();45 return m.impl.acquire();
45}46}
4647
48/// Release the mutex. Prefer Held.release() if available.
49pub fn releaseDirect(m: *Mutex) void {
50 return m.impl.releaseDirect();
51}
52
53/// A held mutex handle. Call release to allow other threads to
54/// take the mutex. Do not call release() more than once.
55/// For more complex scenarios, this handle can be discarded
56/// and Mutex.releaseDirect can be called instead.
57pub const Held = Impl.Held;
58
47const Impl = if (builtin.single_threaded)59const Impl = if (builtin.single_threaded)
48 Dummy60 Dummy
49else if (builtin.os.tag == .windows)61else if (builtin.os.tag == .windows)
...@@ -53,6 +65,32 @@ else if (std.Thread.use_pthreads)...@@ -53,6 +65,32 @@ else if (std.Thread.use_pthreads)
53else65else
54 AtomicMutex;66 AtomicMutex;
5567
68fn HeldInterface(comptime MutexType: type) type {
69 return struct {
70 const Mixin = @This();
71 pub const Held = struct {
72 mutex: *MutexType,
73
74 pub fn release(held: Mixin.Held) void {
75 held.mutex.releaseDirect();
76 }
77 };
78
79 pub fn tryAcquire(m: *MutexType) ?Mixin.Held {
80 if (m.tryAcquireDirect()) {
81 return Mixin.Held{ .mutex = m };
82 } else {
83 return null;
84 }
85 }
86
87 pub fn acquire(m: *MutexType) Mixin.Held {
88 m.acquireDirect();
89 return Mixin.Held{ .mutex = m };
90 }
91 };
92}
93
56pub const AtomicMutex = struct {94pub const AtomicMutex = struct {
57 state: State = .unlocked,95 state: State = .unlocked,
5896
...@@ -62,39 +100,32 @@ pub const AtomicMutex = struct {...@@ -62,39 +100,32 @@ pub const AtomicMutex = struct {
62 waiting,100 waiting,
63 };101 };
64102
65 pub const Held = struct {103 pub usingnamespace HeldInterface(@This());
66 mutex: *AtomicMutex,
67104
68 pub fn release(held: Held) void {105 fn tryAcquireDirect(m: *AtomicMutex) bool {
69 switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) {106 return @cmpxchgStrong(
70 .unlocked => unreachable,
71 .locked => {},
72 .waiting => held.mutex.unlockSlow(),
73 }
74 }
75 };
76
77 pub fn tryAcquire(m: *AtomicMutex) ?Held {
78 if (@cmpxchgStrong(
79 State,107 State,
80 &m.state,108 &m.state,
81 .unlocked,109 .unlocked,
82 .locked,110 .locked,
83 .Acquire,111 .Acquire,
84 .Monotonic,112 .Monotonic,
85 ) == null) {113 ) == null;
86 return Held{ .mutex = m };
87 } else {
88 return null;
89 }
90 }114 }
91115
92 pub fn acquire(m: *AtomicMutex) Held {116 fn acquireDirect(m: *AtomicMutex) void {
93 switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) {117 switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) {
94 .unlocked => {},118 .unlocked => {},
95 else => |s| m.lockSlow(s),119 else => |s| m.lockSlow(s),
96 }120 }
97 return Held{ .mutex = m };121 }
122
123 fn releaseDirect(m: *AtomicMutex) void {
124 switch (@atomicRmw(State, &m.state, .Xchg, .unlocked, .Release)) {
125 .unlocked => unreachable,
126 .locked => {},
127 .waiting => m.unlockSlow(),
128 }
98 }129 }
99130
100 fn lockSlow(m: *AtomicMutex, current_state: State) void {131 fn lockSlow(m: *AtomicMutex, current_state: State) void {
...@@ -171,36 +202,20 @@ pub const AtomicMutex = struct {...@@ -171,36 +202,20 @@ pub const AtomicMutex = struct {
171pub const PthreadMutex = struct {202pub const PthreadMutex = struct {
172 pthread_mutex: std.c.pthread_mutex_t = .{},203 pthread_mutex: std.c.pthread_mutex_t = .{},
173204
174 pub const Held = struct {205 pub usingnamespace HeldInterface(@This());
175 mutex: *PthreadMutex,
176
177 pub fn release(held: Held) void {
178 switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) {
179 .SUCCESS => return,
180 .INVAL => unreachable,
181 .AGAIN => unreachable,
182 .PERM => unreachable,
183 else => unreachable,
184 }
185 }
186 };
187206
188 /// Try to acquire the mutex without blocking. Returns null if207 /// Try to acquire the mutex without blocking. Returns true if
189 /// the mutex is unavailable. Otherwise returns Held. Call208 /// the mutex is unavailable. Otherwise returns false. Call
190 /// release on Held.209 /// release when done.
191 pub fn tryAcquire(m: *PthreadMutex) ?Held {210 fn tryAcquireDirect(m: *PthreadMutex) bool {
192 if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == .SUCCESS) {211 return std.c.pthread_mutex_trylock(&m.pthread_mutex) == .SUCCESS;
193 return Held{ .mutex = m };
194 } else {
195 return null;
196 }
197 }212 }
198213
199 /// Acquire the mutex. Will deadlock if the mutex is already214 /// Acquire the mutex. Will deadlock if the mutex is already
200 /// held by the calling thread.215 /// held by the calling thread.
201 pub fn acquire(m: *PthreadMutex) Held {216 fn acquireDirect(m: *PthreadMutex) void {
202 switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) {217 switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) {
203 .SUCCESS => return Held{ .mutex = m },218 .SUCCESS => {},
204 .INVAL => unreachable,219 .INVAL => unreachable,
205 .BUSY => unreachable,220 .BUSY => unreachable,
206 .AGAIN => unreachable,221 .AGAIN => unreachable,
...@@ -209,6 +224,16 @@ pub const PthreadMutex = struct {...@@ -209,6 +224,16 @@ pub const PthreadMutex = struct {
209 else => unreachable,224 else => unreachable,
210 }225 }
211 }226 }
227
228 fn releaseDirect(m: *PthreadMutex) void {
229 switch (std.c.pthread_mutex_unlock(&m.pthread_mutex)) {
230 .SUCCESS => return,
231 .INVAL => unreachable,
232 .AGAIN => unreachable,
233 .PERM => unreachable,
234 else => unreachable,
235 }
236 }
212};237};
213238
214/// This has the sematics as `Mutex`, however it does not actually do any239/// This has the sematics as `Mutex`, however it does not actually do any
...@@ -216,58 +241,50 @@ pub const PthreadMutex = struct {...@@ -216,58 +241,50 @@ pub const PthreadMutex = struct {
216pub const Dummy = struct {241pub const Dummy = struct {
217 lock: @TypeOf(lock_init) = lock_init,242 lock: @TypeOf(lock_init) = lock_init,
218243
219 const lock_init = if (std.debug.runtime_safety) false else {};244 pub usingnamespace HeldInterface(@This());
220
221 pub const Held = struct {
222 mutex: *Dummy,
223245
224 pub fn release(held: Held) void {246 const lock_init = if (std.debug.runtime_safety) false else {};
225 if (std.debug.runtime_safety) {
226 held.mutex.lock = false;
227 }
228 }
229 };
230247
231 /// Try to acquire the mutex without blocking. Returns null if248 /// Try to acquire the mutex without blocking. Returns false if
232 /// the mutex is unavailable. Otherwise returns Held. Call249 /// the mutex is unavailable. Otherwise returns true.
233 /// release on Held.250 fn tryAcquireDirect(m: *Dummy) bool {
234 pub fn tryAcquire(m: *Dummy) ?Held {
235 if (std.debug.runtime_safety) {251 if (std.debug.runtime_safety) {
236 if (m.lock) return null;252 if (m.lock) return false;
237 m.lock = true;253 m.lock = true;
238 }254 }
239 return Held{ .mutex = m };255 return true;
240 }256 }
241257
242 /// Acquire the mutex. Will deadlock if the mutex is already258 /// Acquire the mutex. Will deadlock if the mutex is already
243 /// held by the calling thread.259 /// held by the calling thread.
244 pub fn acquire(m: *Dummy) Held {260 fn acquireDirect(m: *Dummy) void {
245 return m.tryAcquire() orelse @panic("deadlock detected");261 if (!m.tryAcquireDirect()) {
262 @panic("deadlock detected");
263 }
264 }
265
266 fn releaseDirect(m: *Dummy) void {
267 if (std.debug.runtime_safety) {
268 m.lock = false;
269 }
246 }270 }
247};271};
248272
249const WindowsMutex = struct {273const WindowsMutex = struct {
250 srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT,274 srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT,
251275
252 pub const Held = struct {276 pub usingnamespace HeldInterface(@This());
253 mutex: *WindowsMutex,
254277
255 pub fn release(held: Held) void {278 fn tryAcquireDirect(m: *WindowsMutex) bool {
256 windows.kernel32.ReleaseSRWLockExclusive(&held.mutex.srwlock);279 return windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE;
257 }
258 };
259
260 pub fn tryAcquire(m: *WindowsMutex) ?Held {
261 if (windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) {
262 return Held{ .mutex = m };
263 } else {
264 return null;
265 }
266 }280 }
267281
268 pub fn acquire(m: *WindowsMutex) Held {282 fn acquireDirect(m: *WindowsMutex) void {
269 windows.kernel32.AcquireSRWLockExclusive(&m.srwlock);283 windows.kernel32.AcquireSRWLockExclusive(&m.srwlock);
270 return Held{ .mutex = m };284 }
285
286 fn releaseDirect(m: *WindowsMutex) void {
287 windows.kernel32.ReleaseSRWLockExclusive(&m.srwlock);
271 }288 }
272};289};
273290
src/Sema.zig+6
...@@ -90,6 +90,7 @@ const LazySrcLoc = Module.LazySrcLoc;...@@ -90,6 +90,7 @@ const LazySrcLoc = Module.LazySrcLoc;
90const RangeSet = @import("RangeSet.zig");90const RangeSet = @import("RangeSet.zig");
91const target_util = @import("target.zig");91const target_util = @import("target.zig");
92const Package = @import("Package.zig");92const Package = @import("Package.zig");
93const crash_report = @import("crash_report.zig");
9394
94pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref);95pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref);
9596
...@@ -153,11 +154,16 @@ pub fn analyzeBody(...@@ -153,11 +154,16 @@ pub fn analyzeBody(
153154
154 var orig_captures: usize = parent_capture_scope.captures.count();155 var orig_captures: usize = parent_capture_scope.captures.count();
155156
157 var crash_info = crash_report.prepAnalyzeBody(sema, block, body);
158 crash_info.push();
159 defer crash_info.pop();
160
156 // We use a while(true) loop here to avoid a redundant way of breaking out of161 // We use a while(true) loop here to avoid a redundant way of breaking out of
157 // the loop. The only way to break out of the loop is with a `noreturn`162 // the loop. The only way to break out of the loop is with a `noreturn`
158 // instruction.163 // instruction.
159 var i: usize = 0;164 var i: usize = 0;
160 const result = while (true) {165 const result = while (true) {
166 crash_info.setBodyIndex(i);
161 const inst = body[i];167 const inst = body[i];
162 const air_inst: Air.Inst.Ref = switch (tags[inst]) {168 const air_inst: Air.Inst.Ref = switch (tags[inst]) {
163 // zig fmt: off169 // zig fmt: off
src/crash_report.zig created+581
...@@ -0,0 +1,581 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const debug = std.debug;
4const os = std.os;
5const io = std.io;
6const print_zir = @import("print_zir.zig");
7
8const Module = @import("Module.zig");
9const Sema = @import("Sema.zig");
10const Zir = @import("Zir.zig");
11
12pub const is_enabled = builtin.mode == .Debug;
13
14/// To use these crash report diagnostics, publish these symbols in your main file.
15/// You will also need to call initialize() on startup, preferably as the very first operation in your program.
16pub const root_decls = struct {
17 pub const panic = if (is_enabled) compilerPanic else std.builtin.default_panic;
18 pub const enable_segfault_handler = if (is_enabled) false else debug.default_enable_segfault_handler;
19};
20
21/// Install signal handlers to identify crashes and report diagnostics.
22pub fn initialize() void {
23 if (is_enabled and debug.have_segfault_handling_support) {
24 attachSegfaultHandler();
25 }
26}
27
28fn En(comptime T: type) type {
29 return if (is_enabled) T else void;
30}
31
32fn en(val: anytype) En(@TypeOf(val)) {
33 return if (is_enabled) val else {};
34}
35
36pub const AnalyzeBody = struct {
37 parent: if (is_enabled) ?*AnalyzeBody else void,
38 sema: En(*Sema),
39 block: En(*Module.Scope.Block),
40 body: En([]const Zir.Inst.Index),
41 body_index: En(usize),
42
43 pub fn push(self: *@This()) void {
44 if (!is_enabled) return;
45 const head = &zir_state;
46 debug.assert(self.parent == null);
47 self.parent = head.*;
48 head.* = self;
49 }
50
51 pub fn pop(self: *@This()) void {
52 if (!is_enabled) return;
53 const head = &zir_state;
54 const old = head.*.?;
55 debug.assert(old == self);
56 head.* = old.parent;
57 }
58
59 pub fn setBodyIndex(self: *@This(), index: usize) void {
60 if (!is_enabled) return;
61 self.body_index = index;
62 }
63};
64
65threadlocal var zir_state: ?*AnalyzeBody = if (is_enabled) null else @compileError("Cannot use zir_state if crash_report is disabled.");
66
67pub fn prepAnalyzeBody(sema: *Sema, block: *Module.Scope.Block, body: []const Zir.Inst.Index) AnalyzeBody {
68 if (is_enabled) {
69 return .{
70 .parent = null,
71 .sema = sema,
72 .block = block,
73 .body = body,
74 .body_index = 0,
75 };
76 } else {
77 if (@sizeOf(AnalyzeBody) != 0)
78 @compileError("AnalyzeBody must have zero size when crash reports are disabled");
79 return undefined;
80 }
81}
82
83fn dumpStatusReport() !void {
84 const anal = zir_state orelse return;
85 // Note: We have the panic mutex here, so we can safely use the global crash heap.
86 var fba = std.heap.FixedBufferAllocator.init(&crash_heap);
87 const allocator = &fba.allocator;
88
89 const stderr = io.getStdErr().writer();
90 const block: *Scope.Block = anal.block;
91
92 try stderr.writeAll("Analyzing ");
93 try writeFullyQualifiedDeclWithFile(block.src_decl, stderr);
94 try stderr.writeAll("\n");
95
96 print_zir.renderInstructionContext(
97 allocator,
98 anal.body,
99 anal.body_index,
100 block.src_decl.getFileScope(),
101 block.src_decl.src_node,
102 6, // indent
103 stderr,
104 ) catch |err| switch (err) {
105 error.OutOfMemory => try stderr.writeAll(" <out of memory dumping zir>\n"),
106 else => |e| return e,
107 };
108 try stderr.writeAll(" For full context, use the command\n zig ast-check -t ");
109 try writeFilePath(block.src_decl.getFileScope(), stderr);
110 try stderr.writeAll("\n\n");
111
112 var parent = anal.parent;
113 while (parent) |curr| {
114 fba.reset();
115 try stderr.writeAll(" in ");
116 try writeFullyQualifiedDeclWithFile(curr.block.src_decl, stderr);
117 try stderr.writeAll("\n > ");
118 print_zir.renderSingleInstruction(
119 allocator,
120 curr.body[curr.body_index],
121 curr.block.src_decl.getFileScope(),
122 curr.block.src_decl.src_node,
123 6, // indent
124 stderr,
125 ) catch |err| switch (err) {
126 error.OutOfMemory => try stderr.writeAll(" <out of memory dumping zir>\n"),
127 else => |e| return e,
128 };
129 try stderr.writeAll("\n");
130
131 parent = curr.parent;
132 }
133
134 try stderr.writeAll("\n");
135}
136
137const Scope = Module.Scope;
138const Decl = Module.Decl;
139
140var crash_heap: [16 * 4096]u8 = undefined;
141
142fn writeFilePath(file: *Scope.File, stream: anytype) !void {
143 if (file.pkg.root_src_directory.path) |path| {
144 try stream.writeAll(path);
145 try stream.writeAll(std.fs.path.sep_str);
146 }
147 try stream.writeAll(file.sub_file_path);
148}
149
150fn writeFullyQualifiedDeclWithFile(decl: *Decl, stream: anytype) !void {
151 try writeFilePath(decl.getFileScope(), stream);
152 try stream.writeAll(": ");
153 try decl.namespace.renderFullyQualifiedName(std.mem.sliceTo(decl.name, 0), stream);
154}
155
156fn compilerPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn {
157 PanicSwitch.preDispatch();
158 @setCold(true);
159 const ret_addr = @returnAddress();
160 const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } };
161 PanicSwitch.dispatch(error_return_trace, stack_ctx, msg);
162}
163
164/// Attaches a global SIGSEGV handler
165pub fn attachSegfaultHandler() void {
166 if (!debug.have_segfault_handling_support) {
167 @compileError("segfault handler not supported for this target");
168 }
169 if (builtin.os.tag == .windows) {
170 _ = os.windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
171 return;
172 }
173 var act = os.Sigaction{
174 .handler = .{ .sigaction = handleSegfaultLinux },
175 .mask = os.empty_sigset,
176 .flags = (os.SA.SIGINFO | os.SA.RESTART | os.SA.RESETHAND),
177 };
178
179 os.sigaction(os.SIG.SEGV, &act, null);
180 os.sigaction(os.SIG.ILL, &act, null);
181 os.sigaction(os.SIG.BUS, &act, null);
182}
183
184fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) noreturn {
185 // TODO: use alarm() here to prevent infinite loops
186 PanicSwitch.preDispatch();
187
188 const addr = switch (builtin.os.tag) {
189 .linux => @ptrToInt(info.fields.sigfault.addr),
190 .freebsd => @ptrToInt(info.addr),
191 .netbsd => @ptrToInt(info.info.reason.fault.addr),
192 .openbsd => @ptrToInt(info.data.fault.addr),
193 .solaris => @ptrToInt(info.reason.fault.addr),
194 else => @compileError("TODO implement handleSegfaultLinux for new linux OS"),
195 };
196
197 var err_buffer: [128]u8 = undefined;
198 const error_msg = switch (sig) {
199 os.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault",
200 os.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction",
201 os.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error",
202 else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error",
203 };
204
205 const stack_ctx: StackContext = switch (builtin.cpu.arch) {
206 .i386 => ctx: {
207 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
208 const ip = @intCast(usize, ctx.mcontext.gregs[os.REG.EIP]);
209 const bp = @intCast(usize, ctx.mcontext.gregs[os.REG.EBP]);
210 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
211 },
212 .x86_64 => ctx: {
213 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
214 const ip = switch (builtin.os.tag) {
215 .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]),
216 .freebsd => @intCast(usize, ctx.mcontext.rip),
217 .openbsd => @intCast(usize, ctx.sc_rip),
218 else => unreachable,
219 };
220 const bp = switch (builtin.os.tag) {
221 .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]),
222 .openbsd => @intCast(usize, ctx.sc_rbp),
223 .freebsd => @intCast(usize, ctx.mcontext.rbp),
224 else => unreachable,
225 };
226 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
227 },
228 .arm => ctx: {
229 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
230 const ip = @intCast(usize, ctx.mcontext.arm_pc);
231 const bp = @intCast(usize, ctx.mcontext.arm_fp);
232 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
233 },
234 .aarch64 => ctx: {
235 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
236 const ip = @intCast(usize, ctx.mcontext.pc);
237 // x29 is the ABI-designated frame pointer
238 const bp = @intCast(usize, ctx.mcontext.regs[29]);
239 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
240 },
241 else => .not_supported,
242 };
243
244 PanicSwitch.dispatch(null, stack_ctx, error_msg);
245}
246
247const WindowsSegfaultMessage = union(enum) {
248 literal: []const u8,
249 segfault: void,
250 illegal_instruction: void,
251};
252
253fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windows.WINAPI) c_long {
254 switch (info.ExceptionRecord.ExceptionCode) {
255 os.windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }),
256 os.windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault),
257 os.windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction),
258 os.windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }),
259 else => return os.windows.EXCEPTION_CONTINUE_SEARCH,
260 }
261}
262
263fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn {
264 PanicSwitch.preDispatch();
265
266 const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) ctx: {
267 const regs = info.ContextRecord.getRegs();
268 break :ctx StackContext{ .exception = .{ .bp = regs.bp, .ip = regs.ip } };
269 } else ctx: {
270 const addr = @ptrToInt(info.ExceptionRecord.ExceptionAddress);
271 break :ctx StackContext{ .current = .{ .ret_addr = addr } };
272 };
273
274 switch (msg) {
275 .literal => |err| PanicSwitch.dispatch(null, stack_ctx, err),
276 .segfault => {
277 const format_item = "Segmentation fault at address 0x{x}";
278 var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large
279 const to_print = std.fmt.bufPrint(&buf, format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable;
280 PanicSwitch.dispatch(null, stack_ctx, to_print);
281 },
282 .illegal_instruction => {
283 const ip: ?usize = switch (stack_ctx) {
284 .exception => |ex| ex.ip,
285 .current => |cur| cur.ret_addr,
286 .not_supported => null,
287 };
288
289 if (ip) |addr| {
290 const format_item = "Illegal instruction at address 0x{x}";
291 var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large
292 const to_print = std.fmt.bufPrint(&buf, format_item, .{addr}) catch unreachable;
293 PanicSwitch.dispatch(null, stack_ctx, to_print);
294 } else {
295 PanicSwitch.dispatch(null, stack_ctx, "Illegal Instruction");
296 }
297 },
298 }
299}
300
301const StackContext = union(enum) {
302 current: struct {
303 ret_addr: ?usize,
304 },
305 exception: struct {
306 bp: usize,
307 ip: usize,
308 },
309 not_supported: void,
310
311 pub fn dumpStackTrace(ctx: @This()) void {
312 switch (ctx) {
313 .current => |ct| {
314 debug.dumpCurrentStackTrace(ct.ret_addr);
315 },
316 .exception => |ex| {
317 debug.dumpStackTraceFromBase(ex.bp, ex.ip);
318 },
319 .not_supported => {
320 const stderr = io.getStdErr().writer();
321 stderr.writeAll("Stack trace not supported on this platform.\n") catch {};
322 },
323 }
324 }
325};
326
327const PanicSwitch = struct {
328 const RecoverStage = enum {
329 initialize,
330 report_stack,
331 release_mutex,
332 release_ref_count,
333 abort,
334 silent_abort,
335 };
336
337 const RecoverVerbosity = enum {
338 message_and_stack,
339 message_only,
340 silent,
341 };
342
343 const PanicState = struct {
344 recover_stage: RecoverStage = .initialize,
345 recover_verbosity: RecoverVerbosity = .message_and_stack,
346 panic_ctx: StackContext = undefined,
347 panic_trace: ?*const std.builtin.StackTrace = null,
348 awaiting_dispatch: bool = false,
349 };
350
351 /// Counter for the number of threads currently panicking.
352 /// Updated atomically before taking the panic_mutex.
353 /// In recoverable cases, the program will not abort
354 /// until all panicking threads have dumped their traces.
355 var panicking: u8 = 0;
356
357 // Locked to avoid interleaving panic messages from multiple threads.
358 var panic_mutex = std.Thread.Mutex{};
359
360 /// Tracks the state of the current panic. If the code within the
361 /// panic triggers a secondary panic, this allows us to recover.
362 threadlocal var panic_state_raw: PanicState = .{};
363
364 /// The segfault handlers above need to do some work before they can dispatch
365 /// this switch. Calling preDispatch() first makes that work fault tolerant.
366 pub fn preDispatch() void {
367 // TODO: We want segfaults to trigger the panic recursively here,
368 // but if there is a segfault accessing this TLS slot it will cause an
369 // infinite loop. We should use `alarm()` to prevent the infinite
370 // loop and maybe also use a non-thread-local global to detect if
371 // it's happening and print a message.
372 var panic_state: *volatile PanicState = &panic_state_raw;
373 if (panic_state.awaiting_dispatch) {
374 dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack");
375 }
376 panic_state.awaiting_dispatch = true;
377 }
378
379 /// This is the entry point to a panic-tolerant panic handler.
380 /// preDispatch() *MUST* be called exactly once before calling this.
381 /// A threadlocal "recover_stage" is updated throughout the process.
382 /// If a panic happens during the panic, the recover_stage will be
383 /// used to select a recover* function to call to resume the panic.
384 /// The recover_verbosity field is used to handle panics while reporting
385 /// panics within panics. If the panic handler triggers a panic, it will
386 /// attempt to log an additional stack trace for the secondary panic. If
387 /// that panics, it will fall back to just logging the panic message. If
388 /// it can't even do that witout panicing, it will recover without logging
389 /// anything about the internal panic. Depending on the state, "recover"
390 /// here may just mean "call abort".
391 pub fn dispatch(
392 trace: ?*const std.builtin.StackTrace,
393 stack_ctx: StackContext,
394 msg: []const u8,
395 ) noreturn {
396 var panic_state: *volatile PanicState = &panic_state_raw;
397 debug.assert(panic_state.awaiting_dispatch);
398 panic_state.awaiting_dispatch = false;
399 nosuspend switch (panic_state.recover_stage) {
400 .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }),
401 .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }),
402 .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }),
403 .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }),
404 .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }),
405 .silent_abort => goTo(abort, .{}),
406 };
407 }
408
409 noinline fn initPanic(
410 state: *volatile PanicState,
411 trace: ?*const std.builtin.StackTrace,
412 stack: StackContext,
413 msg: []const u8,
414 ) noreturn {
415 // use a temporary so there's only one volatile store
416 const new_state = PanicState{
417 .recover_stage = .abort,
418 .panic_ctx = stack,
419 .panic_trace = trace,
420 };
421 state.* = new_state;
422
423 _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst);
424
425 state.recover_stage = .release_ref_count;
426
427 _ = panic_mutex.acquire();
428
429 state.recover_stage = .release_mutex;
430
431 const stderr = io.getStdErr().writer();
432 if (builtin.single_threaded) {
433 stderr.print("panic: ", .{}) catch goTo(releaseMutex, .{state});
434 } else {
435 const current_thread_id = std.Thread.getCurrentId();
436 stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state});
437 }
438 stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state});
439
440 state.recover_stage = .report_stack;
441
442 dumpStatusReport() catch |err| {
443 stderr.print("\nIntercepted error.{} while dumping current state. Continuing...\n", .{err}) catch {};
444 };
445
446 goTo(reportStack, .{state});
447 }
448
449 noinline fn recoverReportStack(
450 state: *volatile PanicState,
451 trace: ?*const std.builtin.StackTrace,
452 stack: StackContext,
453 msg: []const u8,
454 ) noreturn {
455 recover(state, trace, stack, msg);
456
457 state.recover_stage = .release_mutex;
458 const stderr = io.getStdErr().writer();
459 stderr.writeAll("\nOriginal Error:\n") catch {};
460 goTo(reportStack, .{state});
461 }
462
463 noinline fn reportStack(state: *volatile PanicState) noreturn {
464 state.recover_stage = .release_mutex;
465
466 if (state.panic_trace) |t| {
467 debug.dumpStackTrace(t.*);
468 }
469 state.panic_ctx.dumpStackTrace();
470
471 goTo(releaseMutex, .{state});
472 }
473
474 noinline fn recoverReleaseMutex(
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 goTo(releaseMutex, .{state});
482 }
483
484 noinline fn releaseMutex(state: *volatile PanicState) noreturn {
485 state.recover_stage = .abort;
486
487 panic_mutex.releaseDirect();
488
489 goTo(releaseRefCount, .{state});
490 }
491
492 noinline fn recoverReleaseRefCount(
493 state: *volatile PanicState,
494 trace: ?*const std.builtin.StackTrace,
495 stack: StackContext,
496 msg: []const u8,
497 ) noreturn {
498 recover(state, trace, stack, msg);
499 goTo(releaseRefCount, .{state});
500 }
501
502 noinline fn releaseRefCount(state: *volatile PanicState) noreturn {
503 state.recover_stage = .abort;
504
505 if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) {
506 // Another thread is panicking, wait for the last one to finish
507 // and call abort()
508
509 // Sleep forever without hammering the CPU
510 var event: std.Thread.StaticResetEvent = .{};
511 event.wait();
512 // This should be unreachable, recurse into recoverAbort.
513 @panic("event.wait() returned");
514 }
515
516 goTo(abort, .{});
517 }
518
519 noinline fn recoverAbort(
520 state: *volatile PanicState,
521 trace: ?*const std.builtin.StackTrace,
522 stack: StackContext,
523 msg: []const u8,
524 ) noreturn {
525 recover(state, trace, stack, msg);
526
527 state.recover_stage = .silent_abort;
528 const stderr = io.getStdErr().writer();
529 stderr.writeAll("Aborting...\n") catch {};
530 goTo(abort, .{});
531 }
532
533 noinline fn abort() noreturn {
534 os.abort();
535 }
536
537 inline fn goTo(comptime func: anytype, args: anytype) noreturn {
538 // TODO: Tailcall is broken right now, but eventually this should be used
539 // to avoid blowing up the stack. It's ok for now though, there are no
540 // cycles in the state machine so the max stack usage is bounded.
541 //@call(.{.modifier = .always_tail}, func, args);
542 @call(.{}, func, args);
543 }
544
545 fn recover(
546 state: *volatile PanicState,
547 trace: ?*const std.builtin.StackTrace,
548 stack: StackContext,
549 msg: []const u8,
550 ) void {
551 switch (state.recover_verbosity) {
552 .message_and_stack => {
553 // lower the verbosity, and restore it at the end if we don't panic.
554 state.recover_verbosity = .message_only;
555
556 const stderr = io.getStdErr().writer();
557 stderr.writeAll("\nPanicked during a panic: ") catch {};
558 stderr.writeAll(msg) catch {};
559 stderr.writeAll("\nInner panic stack:\n") catch {};
560 if (trace) |t| {
561 debug.dumpStackTrace(t.*);
562 }
563 stack.dumpStackTrace();
564
565 state.recover_verbosity = .message_and_stack;
566 },
567 .message_only => {
568 state.recover_verbosity = .silent;
569
570 const stderr = io.getStdErr().writer();
571 stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {};
572 stderr.writeAll(msg) catch {};
573 stderr.writeAll("\n") catch {};
574
575 // If we succeed, restore all the way to dumping the stack.
576 state.recover_verbosity = .message_and_stack;
577 },
578 .silent => {},
579 }
580 }
581};
src/main.zig+6
...@@ -20,6 +20,10 @@ const translate_c = @import("translate_c.zig");...@@ -20,6 +20,10 @@ const translate_c = @import("translate_c.zig");
20const Cache = @import("Cache.zig");20const Cache = @import("Cache.zig");
21const target_util = @import("target.zig");21const target_util = @import("target.zig");
22const ThreadPool = @import("ThreadPool.zig");22const ThreadPool = @import("ThreadPool.zig");
23const crash_report = @import("crash_report.zig");
24
25// Crash report needs to override the panic handler and other root decls
26pub usingnamespace crash_report.root_decls;
2327
24pub fn fatal(comptime format: []const u8, args: anytype) noreturn {28pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
25 std.log.emerg(format, args);29 std.log.emerg(format, args);
...@@ -134,6 +138,8 @@ var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{...@@ -134,6 +138,8 @@ var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{
134}){};138}){};
135139
136pub fn main() anyerror!void {140pub fn main() anyerror!void {
141 crash_report.initialize();
142
137 var gpa_need_deinit = false;143 var gpa_need_deinit = false;
138 const gpa = gpa: {144 const gpa = gpa: {
139 if (!std.builtin.link_libc) {145 if (!std.builtin.link_libc) {
src/print_zir.zig+203-139
...@@ -24,15 +24,20 @@ pub fn renderAsTextToFile(...@@ -24,15 +24,20 @@ pub fn renderAsTextToFile(
24 .code = scope_file.zir,24 .code = scope_file.zir,
25 .indent = 0,25 .indent = 0,
26 .parent_decl_node = 0,26 .parent_decl_node = 0,
27 .recurse_decls = true,
28 .recurse_blocks = true,
27 };29 };
2830
31 var raw_stream = std.io.bufferedWriter(fs_file.writer());
32 const stream = raw_stream.writer();
33
29 const main_struct_inst = Zir.main_struct_inst;34 const main_struct_inst = Zir.main_struct_inst;
30 try fs_file.writer().print("%{d} ", .{main_struct_inst});35 try stream.print("%{d} ", .{main_struct_inst});
31 try writer.writeInstToStream(fs_file.writer(), main_struct_inst);36 try writer.writeInstToStream(stream, main_struct_inst);
32 try fs_file.writeAll("\n");37 try stream.writeAll("\n");
33 const imports_index = scope_file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)];38 const imports_index = scope_file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)];
34 if (imports_index != 0) {39 if (imports_index != 0) {
35 try fs_file.writeAll("Imports:\n");40 try stream.writeAll("Imports:\n");
3641
37 const extra = scope_file.zir.extraData(Zir.Inst.Imports, imports_index);42 const extra = scope_file.zir.extraData(Zir.Inst.Imports, imports_index);
38 var import_i: u32 = 0;43 var import_i: u32 = 0;
...@@ -44,13 +49,74 @@ pub fn renderAsTextToFile(...@@ -44,13 +49,74 @@ pub fn renderAsTextToFile(
4449
45 const src: LazySrcLoc = .{ .token_abs = item.data.token };50 const src: LazySrcLoc = .{ .token_abs = item.data.token };
46 const import_path = scope_file.zir.nullTerminatedString(item.data.name);51 const import_path = scope_file.zir.nullTerminatedString(item.data.name);
47 try fs_file.writer().print(" @import(\"{}\") ", .{52 try stream.print(" @import(\"{}\") ", .{
48 std.zig.fmtEscapes(import_path),53 std.zig.fmtEscapes(import_path),
49 });54 });
50 try writer.writeSrc(fs_file.writer(), src);55 try writer.writeSrc(stream, src);
51 try fs_file.writer().writeAll("\n");56 try stream.writeAll("\n");
52 }57 }
53 }58 }
59
60 try raw_stream.flush();
61}
62
63pub fn renderInstructionContext(
64 gpa: *Allocator,
65 block: []const Zir.Inst.Index,
66 block_index: usize,
67 scope_file: *Module.Scope.File,
68 parent_decl_node: Ast.Node.Index,
69 indent: u32,
70 stream: anytype,
71) !void {
72 var arena = std.heap.ArenaAllocator.init(gpa);
73 defer arena.deinit();
74
75 var writer: Writer = .{
76 .gpa = gpa,
77 .arena = &arena.allocator,
78 .file = scope_file,
79 .code = scope_file.zir,
80 .indent = if (indent < 2) 2 else indent,
81 .parent_decl_node = parent_decl_node,
82 .recurse_decls = false,
83 .recurse_blocks = true,
84 };
85
86 try writer.writeBody(stream, block[0..block_index]);
87 try stream.writeByteNTimes(' ', writer.indent - 2);
88 try stream.print("> %{d} ", .{block[block_index]});
89 try writer.writeInstToStream(stream, block[block_index]);
90 try stream.writeByte('\n');
91 if (block_index + 1 < block.len) {
92 try writer.writeBody(stream, block[block_index + 1 ..]);
93 }
94}
95
96pub fn renderSingleInstruction(
97 gpa: *Allocator,
98 inst: Zir.Inst.Index,
99 scope_file: *Module.Scope.File,
100 parent_decl_node: Ast.Node.Index,
101 indent: u32,
102 stream: anytype,
103) !void {
104 var arena = std.heap.ArenaAllocator.init(gpa);
105 defer arena.deinit();
106
107 var writer: Writer = .{
108 .gpa = gpa,
109 .arena = &arena.allocator,
110 .file = scope_file,
111 .code = scope_file.zir,
112 .indent = indent,
113 .parent_decl_node = parent_decl_node,
114 .recurse_decls = false,
115 .recurse_blocks = false,
116 };
117
118 try stream.print("%{d} ", .{inst});
119 try writer.writeInstToStream(stream, inst);
54}120}
55121
56const Writer = struct {122const Writer = struct {
...@@ -59,7 +125,9 @@ const Writer = struct {...@@ -59,7 +125,9 @@ const Writer = struct {
59 file: *Module.Scope.File,125 file: *Module.Scope.File,
60 code: Zir,126 code: Zir,
61 indent: u32,127 indent: u32,
62 parent_decl_node: u32,128 parent_decl_node: Ast.Node.Index,
129 recurse_decls: bool,
130 recurse_blocks: bool,
63131
64 fn relativeToNodeIndex(self: *Writer, offset: i32) Ast.Node.Index {132 fn relativeToNodeIndex(self: *Writer, offset: i32) Ast.Node.Index {
65 return @bitCast(Ast.Node.Index, offset + @bitCast(i32, self.parent_decl_node));133 return @bitCast(Ast.Node.Index, offset + @bitCast(i32, self.parent_decl_node));
...@@ -567,12 +635,8 @@ const Writer = struct {...@@ -567,12 +635,8 @@ const Writer = struct {
567 try stream.print("\"{}\", ", .{635 try stream.print("\"{}\", ", .{
568 std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)),636 std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)),
569 });637 });
570 try stream.writeAll("{\n");638 try self.writeBracedBody(stream, body);
571 self.indent += 2;639 try stream.writeAll(") ");
572 try self.writeBody(stream, body);
573 self.indent -= 2;
574 try stream.writeByteNTimes(' ', self.indent);
575 try stream.writeAll("}) ");
576 try self.writeSrc(stream, inst_data.src());640 try self.writeSrc(stream, inst_data.src());
577 }641 }
578642
...@@ -881,12 +945,8 @@ const Writer = struct {...@@ -881,12 +945,8 @@ const Writer = struct {
881 const inst_data = self.code.instructions.items(.data)[inst].pl_node;945 const inst_data = self.code.instructions.items(.data)[inst].pl_node;
882 const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);946 const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);
883 const body = self.code.extra[extra.end..][0..extra.data.body_len];947 const body = self.code.extra[extra.end..][0..extra.data.body_len];
884 try stream.writeAll("{\n");948 try self.writeBracedBody(stream, body);
885 self.indent += 2;949 try stream.writeAll(") ");
886 try self.writeBody(stream, body);
887 self.indent -= 2;
888 try stream.writeByteNTimes(' ', self.indent);
889 try stream.writeAll("}) ");
890 }950 }
891951
892 fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {952 fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
...@@ -895,17 +955,11 @@ const Writer = struct {...@@ -895,17 +955,11 @@ const Writer = struct {
895 const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len];955 const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len];
896 const else_body = self.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];956 const else_body = self.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
897 try self.writeInstRef(stream, extra.data.condition);957 try self.writeInstRef(stream, extra.data.condition);
898 try stream.writeAll(", {\n");958 try stream.writeAll(", ");
899 self.indent += 2;959 try self.writeBracedBody(stream, then_body);
900 try self.writeBody(stream, then_body);960 try stream.writeAll(", ");
901 self.indent -= 2;961 try self.writeBracedBody(stream, else_body);
902 try stream.writeByteNTimes(' ', self.indent);962 try stream.writeAll(") ");
903 try stream.writeAll("}, {\n");
904 self.indent += 2;
905 try self.writeBody(stream, else_body);
906 self.indent -= 2;
907 try stream.writeByteNTimes(' ', self.indent);
908 try stream.writeAll("}) ");
909 try self.writeSrc(stream, inst_data.src());963 try self.writeSrc(stream, inst_data.src());
910 }964 }
911965
...@@ -963,17 +1017,10 @@ const Writer = struct {...@@ -963,17 +1017,10 @@ const Writer = struct {
963 } else {1017 } else {
964 const prev_parent_decl_node = self.parent_decl_node;1018 const prev_parent_decl_node = self.parent_decl_node;
965 if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);1019 if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
966 self.indent += 2;1020 try self.writeBracedDecl(stream, body);
967 if (body.len == 0) {1021 try stream.writeAll(", {\n");
968 try stream.writeAll("{}, {\n");
969 } else {
970 try stream.writeAll("{\n");
971 try self.writeBody(stream, body);
972
973 try stream.writeByteNTimes(' ', self.indent - 2);
974 try stream.writeAll("}, {\n");
975 }
9761022
1023 self.indent += 2;
977 const bits_per_field = 4;1024 const bits_per_field = 4;
978 const fields_per_u32 = 32 / bits_per_field;1025 const fields_per_u32 = 32 / bits_per_field;
979 const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;1026 const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
...@@ -1096,17 +1143,10 @@ const Writer = struct {...@@ -1096,17 +1143,10 @@ const Writer = struct {
10961143
1097 const prev_parent_decl_node = self.parent_decl_node;1144 const prev_parent_decl_node = self.parent_decl_node;
1098 if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);1145 if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
1099 self.indent += 2;1146 try self.writeBracedDecl(stream, body);
1100 if (body.len == 0) {1147 try stream.writeAll(", {\n");
1101 try stream.writeAll("{}, {\n");
1102 } else {
1103 try stream.writeAll("{\n");
1104 try self.writeBody(stream, body);
1105
1106 try stream.writeByteNTimes(' ', self.indent - 2);
1107 try stream.writeAll("}, {\n");
1108 }
11091148
1149 self.indent += 2;
1110 const bits_per_field = 4;1150 const bits_per_field = 4;
1111 const fields_per_u32 = 32 / bits_per_field;1151 const fields_per_u32 = 32 / bits_per_field;
1112 const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;1152 const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
...@@ -1251,18 +1291,25 @@ const Writer = struct {...@@ -1251,18 +1291,25 @@ const Writer = struct {
1251 try stream.writeAll(")");1291 try stream.writeAll(")");
1252 }1292 }
1253 }1293 }
1254 const tag = self.code.instructions.items(.tag)[decl_index];
1255 try stream.print(" line({d}) hash({}): %{d} = {s}(", .{
1256 line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag),
1257 });
12581294
1259 const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node;1295 if (self.recurse_decls) {
1260 const sub_decl_node_off = decl_block_inst_data.src_node;1296 const tag = self.code.instructions.items(.tag)[decl_index];
1261 self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off);1297 try stream.print(" line({d}) hash({}): %{d} = {s}(", .{
1262 try self.writePlNodeBlockWithoutSrc(stream, decl_index);1298 line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag),
1263 self.parent_decl_node = parent_decl_node;1299 });
1264 try self.writeSrc(stream, decl_block_inst_data.src());1300
1265 try stream.writeAll("\n");1301 const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node;
1302 const sub_decl_node_off = decl_block_inst_data.src_node;
1303 self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off);
1304 try self.writePlNodeBlockWithoutSrc(stream, decl_index);
1305 self.parent_decl_node = parent_decl_node;
1306 try self.writeSrc(stream, decl_block_inst_data.src());
1307 try stream.writeAll("\n");
1308 } else {
1309 try stream.print(" line({d}) hash({}): %{d} = ...\n", .{
1310 line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index,
1311 });
1312 }
1266 }1313 }
1267 return extra_index;1314 return extra_index;
1268 }1315 }
...@@ -1329,17 +1376,10 @@ const Writer = struct {...@@ -1329,17 +1376,10 @@ const Writer = struct {
1329 } else {1376 } else {
1330 const prev_parent_decl_node = self.parent_decl_node;1377 const prev_parent_decl_node = self.parent_decl_node;
1331 if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);1378 if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
1332 self.indent += 2;1379 try self.writeBracedDecl(stream, body);
1333 if (body.len == 0) {1380 try stream.writeAll(", {\n");
1334 try stream.writeAll("{}, {\n");
1335 } else {
1336 try stream.writeAll("{\n");
1337 try self.writeBody(stream, body);
1338
1339 try stream.writeByteNTimes(' ', self.indent - 2);
1340 try stream.writeAll("}, {\n");
1341 }
13421381
1382 self.indent += 2;
1343 const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;1383 const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;
1344 const body_end = extra_index;1384 const body_end = extra_index;
1345 extra_index += bit_bags_count;1385 extra_index += bit_bags_count;
...@@ -1463,18 +1503,18 @@ const Writer = struct {...@@ -1463,18 +1503,18 @@ const Writer = struct {
14631503
1464 try self.writeInstRef(stream, extra.data.operand);1504 try self.writeInstRef(stream, extra.data.operand);
14651505
1506 self.indent += 2;
1507
1466 if (special.body.len != 0) {1508 if (special.body.len != 0) {
1467 const prong_name = switch (special_prong) {1509 const prong_name = switch (special_prong) {
1468 .@"else" => "else",1510 .@"else" => "else",
1469 .under => "_",1511 .under => "_",
1470 else => unreachable,1512 else => unreachable,
1471 };1513 };
1472 try stream.print(", {s} => {{\n", .{prong_name});1514 try stream.writeAll(",\n");
1473 self.indent += 2;
1474 try self.writeBody(stream, special.body);
1475 self.indent -= 2;
1476 try stream.writeByteNTimes(' ', self.indent);1515 try stream.writeByteNTimes(' ', self.indent);
1477 try stream.writeAll("}");1516 try stream.print("{s} => ", .{prong_name});
1517 try self.writeBracedBody(stream, special.body);
1478 }1518 }
14791519
1480 var extra_index: usize = special.end;1520 var extra_index: usize = special.end;
...@@ -1488,16 +1528,16 @@ const Writer = struct {...@@ -1488,16 +1528,16 @@ const Writer = struct {
1488 const body = self.code.extra[extra_index..][0..body_len];1528 const body = self.code.extra[extra_index..][0..body_len];
1489 extra_index += body_len;1529 extra_index += body_len;
14901530
1491 try stream.writeAll(", ");1531 try stream.writeAll(",\n");
1492 try self.writeInstRef(stream, item_ref);
1493 try stream.writeAll(" => {\n");
1494 self.indent += 2;
1495 try self.writeBody(stream, body);
1496 self.indent -= 2;
1497 try stream.writeByteNTimes(' ', self.indent);1532 try stream.writeByteNTimes(' ', self.indent);
1498 try stream.writeAll("}");1533 try self.writeInstRef(stream, item_ref);
1534 try stream.writeAll(" => ");
1535 try self.writeBracedBody(stream, body);
1499 }1536 }
1500 }1537 }
1538
1539 self.indent -= 2;
1540
1501 try stream.writeAll(") ");1541 try stream.writeAll(") ");
1502 try self.writeSrc(stream, inst_data.src());1542 try self.writeSrc(stream, inst_data.src());
1503 }1543 }
...@@ -1527,18 +1567,18 @@ const Writer = struct {...@@ -1527,18 +1567,18 @@ const Writer = struct {
15271567
1528 try self.writeInstRef(stream, extra.data.operand);1568 try self.writeInstRef(stream, extra.data.operand);
15291569
1570 self.indent += 2;
1571
1530 if (special.body.len != 0) {1572 if (special.body.len != 0) {
1531 const prong_name = switch (special_prong) {1573 const prong_name = switch (special_prong) {
1532 .@"else" => "else",1574 .@"else" => "else",
1533 .under => "_",1575 .under => "_",
1534 else => unreachable,1576 else => unreachable,
1535 };1577 };
1536 try stream.print(", {s} => {{\n", .{prong_name});1578 try stream.writeAll(",\n");
1537 self.indent += 2;
1538 try self.writeBody(stream, special.body);
1539 self.indent -= 2;
1540 try stream.writeByteNTimes(' ', self.indent);1579 try stream.writeByteNTimes(' ', self.indent);
1541 try stream.writeAll("}");1580 try stream.print("{s} => ", .{prong_name});
1581 try self.writeBracedBody(stream, special.body);
1542 }1582 }
15431583
1544 var extra_index: usize = special.end;1584 var extra_index: usize = special.end;
...@@ -1552,14 +1592,11 @@ const Writer = struct {...@@ -1552,14 +1592,11 @@ const Writer = struct {
1552 const body = self.code.extra[extra_index..][0..body_len];1592 const body = self.code.extra[extra_index..][0..body_len];
1553 extra_index += body_len;1593 extra_index += body_len;
15541594
1555 try stream.writeAll(", ");1595 try stream.writeAll(",\n");
1556 try self.writeInstRef(stream, item_ref);
1557 try stream.writeAll(" => {\n");
1558 self.indent += 2;
1559 try self.writeBody(stream, body);
1560 self.indent -= 2;
1561 try stream.writeByteNTimes(' ', self.indent);1596 try stream.writeByteNTimes(' ', self.indent);
1562 try stream.writeAll("}");1597 try self.writeInstRef(stream, item_ref);
1598 try stream.writeAll(" => ");
1599 try self.writeBracedBody(stream, body);
1563 }1600 }
1564 }1601 }
1565 {1602 {
...@@ -1574,8 +1611,11 @@ const Writer = struct {...@@ -1574,8 +1611,11 @@ const Writer = struct {
1574 const items = self.code.refSlice(extra_index, items_len);1611 const items = self.code.refSlice(extra_index, items_len);
1575 extra_index += items_len;1612 extra_index += items_len;
15761613
1577 for (items) |item_ref| {1614 try stream.writeAll(",\n");
1578 try stream.writeAll(", ");1615 try stream.writeByteNTimes(' ', self.indent);
1616
1617 for (items) |item_ref, item_i| {
1618 if (item_i != 0) try stream.writeAll(", ");
1579 try self.writeInstRef(stream, item_ref);1619 try self.writeInstRef(stream, item_ref);
1580 }1620 }
15811621
...@@ -1586,7 +1626,9 @@ const Writer = struct {...@@ -1586,7 +1626,9 @@ const Writer = struct {
1586 const item_last = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);1626 const item_last = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
1587 extra_index += 1;1627 extra_index += 1;
15881628
1589 try stream.writeAll(", ");1629 if (range_i != 0 or items.len != 0) {
1630 try stream.writeAll(", ");
1631 }
1590 try self.writeInstRef(stream, item_first);1632 try self.writeInstRef(stream, item_first);
1591 try stream.writeAll("...");1633 try stream.writeAll("...");
1592 try self.writeInstRef(stream, item_last);1634 try self.writeInstRef(stream, item_last);
...@@ -1594,14 +1636,13 @@ const Writer = struct {...@@ -1594,14 +1636,13 @@ const Writer = struct {
15941636
1595 const body = self.code.extra[extra_index..][0..body_len];1637 const body = self.code.extra[extra_index..][0..body_len];
1596 extra_index += body_len;1638 extra_index += body_len;
1597 try stream.writeAll(" => {\n");1639 try stream.writeAll(" => ");
1598 self.indent += 2;1640 try self.writeBracedBody(stream, body);
1599 try self.writeBody(stream, body);
1600 self.indent -= 2;
1601 try stream.writeByteNTimes(' ', self.indent);
1602 try stream.writeAll("}");
1603 }1641 }
1604 }1642 }
1643
1644 self.indent -= 2;
1645
1605 try stream.writeAll(") ");1646 try stream.writeAll(") ");
1606 try self.writeSrc(stream, inst_data.src());1647 try self.writeSrc(stream, inst_data.src());
1607 }1648 }
...@@ -1796,12 +1837,8 @@ const Writer = struct {...@@ -1796,12 +1837,8 @@ const Writer = struct {
1796 const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);1837 const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);
1797 const body = self.code.extra[extra.end..][0..extra.data.body_len];1838 const body = self.code.extra[extra.end..][0..extra.data.body_len];
1798 try self.writeInstRef(stream, inst_data.lhs);1839 try self.writeInstRef(stream, inst_data.lhs);
1799 try stream.writeAll(", {\n");1840 try stream.writeAll(", ");
1800 self.indent += 2;1841 try self.writeBracedBody(stream, body);
1801 try self.writeBody(stream, body);
1802 self.indent -= 2;
1803 try stream.writeByteNTimes(' ', self.indent);
1804 try stream.writeAll("})");
1805 }1842 }
18061843
1807 fn writeIntType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {1844 fn writeIntType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
...@@ -1846,12 +1883,8 @@ const Writer = struct {...@@ -1846,12 +1883,8 @@ const Writer = struct {
1846 if (ret_ty_body.len == 0) {1883 if (ret_ty_body.len == 0) {
1847 try stream.writeAll("ret_ty=void");1884 try stream.writeAll("ret_ty=void");
1848 } else {1885 } else {
1849 try stream.writeAll("ret_ty={\n");1886 try stream.writeAll("ret_ty=");
1850 self.indent += 2;1887 try self.writeBracedBody(stream, ret_ty_body);
1851 try self.writeBody(stream, ret_ty_body);
1852 self.indent -= 2;
1853 try stream.writeByteNTimes(' ', self.indent);
1854 try stream.writeAll("}");
1855 }1888 }
18561889
1857 try self.writeOptionalInstRef(stream, ", cc=", cc);1890 try self.writeOptionalInstRef(stream, ", cc=", cc);
...@@ -1860,16 +1893,9 @@ const Writer = struct {...@@ -1860,16 +1893,9 @@ const Writer = struct {
1860 try self.writeFlag(stream, ", extern", is_extern);1893 try self.writeFlag(stream, ", extern", is_extern);
1861 try self.writeFlag(stream, ", inferror", inferred_error_set);1894 try self.writeFlag(stream, ", inferror", inferred_error_set);
18621895
1863 if (body.len == 0) {1896 try stream.writeAll(", body=");
1864 try stream.writeAll(", body={}) ");1897 try self.writeBracedBody(stream, body);
1865 } else {1898 try stream.writeAll(") ");
1866 try stream.writeAll(", body={\n");
1867 self.indent += 2;
1868 try self.writeBody(stream, body);
1869 self.indent -= 2;
1870 try stream.writeByteNTimes(' ', self.indent);
1871 try stream.writeAll("}) ");
1872 }
1873 if (body.len != 0) {1899 if (body.len != 0) {
1874 try stream.print("(lbrace={d}:{d},rbrace={d}:{d}) ", .{1900 try stream.print("(lbrace={d}:{d},rbrace={d}:{d}) ", .{
1875 src_locs.lbrace_line, @truncate(u16, src_locs.columns),1901 src_locs.lbrace_line, @truncate(u16, src_locs.columns),
...@@ -1929,18 +1955,19 @@ const Writer = struct {...@@ -1929,18 +1955,19 @@ const Writer = struct {
1929 }1955 }
19301956
1931 fn writeSrc(self: *Writer, stream: anytype, src: LazySrcLoc) !void {1957 fn writeSrc(self: *Writer, stream: anytype, src: LazySrcLoc) !void {
1932 const tree = self.file.tree;1958 if (self.file.tree_loaded) {
1933 const src_loc: Module.SrcLoc = .{1959 const tree = self.file.tree;
1934 .file_scope = self.file,1960 const src_loc: Module.SrcLoc = .{
1935 .parent_decl_node = self.parent_decl_node,1961 .file_scope = self.file,
1936 .lazy = src,1962 .parent_decl_node = self.parent_decl_node,
1937 };1963 .lazy = src,
1938 // Caller must ensure AST tree is loaded.1964 };
1939 const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable;1965 const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable;
1940 const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off);1966 const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off);
1941 try stream.print("{s}:{d}:{d}", .{1967 try stream.print("{s}:{d}:{d}", .{
1942 @tagName(src), delta_line.line + 1, delta_line.column + 1,1968 @tagName(src), delta_line.line + 1, delta_line.column + 1,
1943 });1969 });
1970 }
1944 }1971 }
19451972
1946 fn writeSrcNode(self: *Writer, stream: anytype, src_node: ?i32) !void {1973 fn writeSrcNode(self: *Writer, stream: anytype, src_node: ?i32) !void {
...@@ -1950,6 +1977,43 @@ const Writer = struct {...@@ -1950,6 +1977,43 @@ const Writer = struct {
1950 return self.writeSrc(stream, src);1977 return self.writeSrc(stream, src);
1951 }1978 }
19521979
1980 fn writeBracedDecl(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void {
1981 try self.writeBracedBodyConditional(stream, body, self.recurse_decls);
1982 }
1983
1984 fn writeBracedBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void {
1985 try self.writeBracedBodyConditional(stream, body, self.recurse_blocks);
1986 }
1987
1988 fn writeBracedBodyConditional(self: *Writer, stream: anytype, body: []const Zir.Inst.Index, enabled: bool) !void {
1989 if (body.len == 0) {
1990 try stream.writeAll("{}");
1991 } else if (enabled) {
1992 try stream.writeAll("{\n");
1993 self.indent += 2;
1994 try self.writeBody(stream, body);
1995 self.indent -= 2;
1996 try stream.writeByteNTimes(' ', self.indent);
1997 try stream.writeAll("}");
1998 } else if (body.len == 1) {
1999 try stream.writeByte('{');
2000 try self.writeInstIndex(stream, body[0]);
2001 try stream.writeByte('}');
2002 } else if (body.len == 2) {
2003 try stream.writeByte('{');
2004 try self.writeInstIndex(stream, body[0]);
2005 try stream.writeAll(", ");
2006 try self.writeInstIndex(stream, body[1]);
2007 try stream.writeByte('}');
2008 } else {
2009 try stream.writeByte('{');
2010 try self.writeInstIndex(stream, body[0]);
2011 try stream.writeAll("..");
2012 try self.writeInstIndex(stream, body[body.len - 1]);
2013 try stream.writeByte('}');
2014 }
2015 }
2016
1953 fn writeBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void {2017 fn writeBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void {
1954 for (body) |inst| {2018 for (body) |inst| {
1955 try stream.writeByteNTimes(' ', self.indent);2019 try stream.writeByteNTimes(' ', self.indent);