diff --git a/lib/compiler_rt/common.zig b/lib/compiler_rt/common.zig index 8a1055267d..db2a55445e 100644 --- a/lib/compiler_rt/common.zig +++ b/lib/compiler_rt/common.zig @@ -75,11 +75,14 @@ pub const gnu_f16_abi = switch (builtin.cpu.arch) { pub const want_sparc_abi = builtin.cpu.arch.isSPARC(); -// Avoid dragging in the runtime safety mechanisms into this .o file, -// unless we're trying to test compiler-rt. -pub fn panic(cause: std.builtin.PanicCause, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { +// Avoid dragging in the runtime safety mechanisms into this .o file, unless +// we're trying to test compiler-rt. +pub const Panic = if (builtin.is_test) std.debug.FormattedPanic else struct {}; + +/// To be deleted after zig1.wasm is updated. +pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { if (builtin.is_test) { - std.debug.defaultPanic(cause, error_return_trace, ret_addr orelse @returnAddress()); + std.debug.defaultPanic(msg, error_return_trace, ret_addr orelse @returnAddress()); } else { unreachable; } diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 704d89dace..5d3eca5f1e 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -761,11 +761,10 @@ pub const TestFn = struct { func: *const fn () anyerror!void, }; -/// This function type is used by the Zig language code generation and -/// therefore must be kept in sync with the compiler implementation. -pub const PanicFn = fn (PanicCause, ?*StackTrace, ?usize) noreturn; +/// Deprecated, use the `Panic` namespace instead. +pub const PanicFn = fn ([]const u8, ?*StackTrace, ?usize) noreturn; -/// The entry point for auto-generated calls by the compiler. +/// Deprecated, use the `Panic` namespace instead. pub const panic: PanicFn = if (@hasDecl(root, "panic")) root.panic else if (@hasDecl(root, "os") and @hasDecl(root.os, "panic")) @@ -773,143 +772,28 @@ else if (@hasDecl(root, "os") and @hasDecl(root.os, "panic")) else std.debug.defaultPanic; -/// This data structure is used by the Zig language code generation and -/// therefore must be kept in sync with the compiler implementation. -pub const PanicCause = union(enum) { - reached_unreachable, - unwrap_null, - cast_to_null, - incorrect_alignment, - invalid_error_code, - cast_truncated_data, - negative_to_unsigned, - integer_overflow, - shl_overflow, - shr_overflow, - divide_by_zero, - exact_division_remainder, - inactive_union_field: InactiveUnionField, - integer_part_out_of_bounds, - corrupt_switch, - shift_rhs_too_big, - invalid_enum_value, - sentinel_mismatch_usize: SentinelMismatchUsize, - sentinel_mismatch_other, - unwrap_error: anyerror, - index_out_of_bounds: IndexOutOfBounds, - start_index_greater_than_end: StartIndexGreaterThanEnd, - for_len_mismatch, - memcpy_len_mismatch, - memcpy_alias, - noreturn_returned, - explicit_call: []const u8, - sentinel_mismatch_isize: SentinelMismatchIsize, +/// This namespace is used by the Zig compiler to emit various kinds of safety +/// panics. These can be overridden by making a public `Panic` namespace in the +/// root source file. +pub const Panic: type = if (@hasDecl(root, "Panic")) + root.Panic +else if (std.builtin.zig_backend == .stage2_riscv64) + std.debug.SimplePanic // https://github.com/ziglang/zig/issues/21519 +else + std.debug.FormattedPanic; - pub const IndexOutOfBounds = struct { - index: usize, - len: usize, - }; - - pub const StartIndexGreaterThanEnd = struct { - start: usize, - end: usize, - }; - - pub const SentinelMismatchUsize = struct { - expected: usize, - found: usize, - }; - - pub const SentinelMismatchIsize = struct { - expected: isize, - found: isize, - }; - - pub const InactiveUnionField = struct { - active: []const u8, - accessed: []const u8, - }; -}; - -pub fn panicSentinelMismatch(expected: anytype, found: @TypeOf(expected)) noreturn { - @branchHint(.cold); - if (builtin.zig_backend == .stage2_riscv64) { - // https://github.com/ziglang/zig/issues/21519 - @trap(); - } - switch (@typeInfo(@TypeOf(expected))) { - .int => |int| switch (int.signedness) { - .unsigned => if (int.bits <= @bitSizeOf(usize)) panic(.{ .sentinel_mismatch_usize = .{ - .expected = expected, - .found = found, - } }, null, @returnAddress()), - .signed => if (int.bits <= @bitSizeOf(isize)) panic(.{ .sentinel_mismatch_isize = .{ - .expected = expected, - .found = found, - } }, null, @returnAddress()), - }, - .@"enum" => |info| switch (@typeInfo(info.tag_type)) { - .int => |int| switch (int.signedness) { - .unsigned => if (int.bits <= @bitSizeOf(usize)) panic(.{ .sentinel_mismatch_usize = .{ - .expected = @intFromEnum(expected), - .found = @intFromEnum(found), - } }, null, @returnAddress()), - .signed => if (int.bits <= @bitSizeOf(isize)) panic(.{ .sentinel_mismatch_isize = .{ - .expected = @intFromEnum(expected), - .found = @intFromEnum(found), - } }, null, @returnAddress()), - }, - else => comptime unreachable, - }, - else => {}, - } - panic(.sentinel_mismatch_other, null, @returnAddress()); -} - -pub fn panicUnwrapError(ert: ?*StackTrace, err: anyerror) noreturn { - @branchHint(.cold); - if (builtin.zig_backend == .stage2_riscv64) { - // https://github.com/ziglang/zig/issues/21519 - @trap(); - } - panic(.{ .unwrap_error = err }, ert, @returnAddress()); -} - -pub fn panicOutOfBounds(index: usize, len: usize) noreturn { - @branchHint(.cold); - if (builtin.zig_backend == .stage2_riscv64) { - // https://github.com/ziglang/zig/issues/21519 - @trap(); - } - panic(.{ .index_out_of_bounds = .{ - .index = index, - .len = len, - } }, null, @returnAddress()); -} - -pub fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn { - @branchHint(.cold); - if (builtin.zig_backend == .stage2_riscv64) { - // https://github.com/ziglang/zig/issues/21519 - @trap(); - } - panic(.{ .start_index_greater_than_end = .{ - .start = start, - .end = end, - } }, null, @returnAddress()); -} - -pub fn panicInactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn { - @branchHint(.cold); - if (builtin.zig_backend == .stage2_riscv64) { - // https://github.com/ziglang/zig/issues/21519 - @trap(); - } - panic(.{ .inactive_union_field = .{ - .active = @tagName(active), - .accessed = @tagName(accessed), - } }, null, @returnAddress()); -} +/// To be deleted after zig1.wasm is updated. +pub const panicSentinelMismatch = Panic.sentinelMismatch; +/// To be deleted after zig1.wasm is updated. +pub const panicUnwrapError = Panic.unwrapError; +/// To be deleted after zig1.wasm is updated. +pub const panicOutOfBounds = Panic.outOfBounds; +/// To be deleted after zig1.wasm is updated. +pub const panicStartGreaterThanEnd = Panic.startGreaterThanEnd; +/// To be deleted after zig1.wasm is updated. +pub const panicInactiveUnionField = Panic.inactiveUnionField; +/// To be deleted after zig1.wasm is updated. +pub const panic_messages = Panic.messages; pub noinline fn returnError(st: *StackTrace) void { @branchHint(.unlikely); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 98bf6b1fd4..0cb201373a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -21,6 +21,9 @@ pub const SelfInfo = @import("debug/SelfInfo.zig"); pub const Info = @import("debug/Info.zig"); pub const Coverage = @import("debug/Coverage.zig"); +pub const FormattedPanic = @import("debug/FormattedPanic.zig"); +pub const SimplePanic = @import("debug/SimplePanic.zig"); + /// Unresolved source locations can be represented with a single `usize` that /// corresponds to a virtual memory address of the program counter. Combined /// with debug information, those values can be converted into a resolved @@ -408,10 +411,16 @@ pub fn assertReadable(slice: []const volatile u8) void { for (slice) |*byte| _ = byte.*; } +/// By including a call to this function, the caller gains an error return trace +/// secret parameter, making `@errorReturnTrace()` more useful. This is not +/// necessary if the function already contains a call to an errorable function +/// elsewhere. +pub fn errorReturnTraceHelper() anyerror!void {} + /// Equivalent to `@panic` but with a formatted message. pub fn panic(comptime format: []const u8, args: anytype) noreturn { @branchHint(.cold); - + errorReturnTraceHelper() catch unreachable; panicExtra(@errorReturnTrace(), @returnAddress(), format, args); } @@ -437,7 +446,7 @@ pub fn panicExtra( break :blk &buf; }, }; - std.builtin.panic(.{ .explicit_call = msg }, trace, ret_addr); + std.builtin.Panic.call(msg, trace, ret_addr); } /// Non-zero whenever the program triggered a panic. @@ -448,11 +457,9 @@ var panicking = std.atomic.Value(u8).init(0); /// This is used to catch and handle panics triggered by the panic handler. threadlocal var panic_stage: usize = 0; -// Dumps a stack trace to standard error, then aborts. -// -// This function avoids a dependency on formatted printing. +/// Dumps a stack trace to standard error, then aborts. pub fn defaultPanic( - cause: std.builtin.PanicCause, + msg: []const u8, error_return_trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize, ) noreturn { @@ -471,18 +478,6 @@ pub fn defaultPanic( @trap(); } - if (builtin.zig_backend == .stage2_riscv64) { - var buffer: [1000]u8 = undefined; - var i: usize = 0; - i += fmtPanicCause(buffer[i..], cause); - buffer[i] = '\n'; - i += 1; - const msg = buffer[0..i]; - lockStdErr(); - io.getStdErr().writeAll(msg) catch {}; - @trap(); - } - switch (builtin.os.tag) { .freestanding => { @trap(); @@ -490,14 +485,10 @@ pub fn defaultPanic( .uefi => { const uefi = std.os.uefi; - var buffer: [1000]u8 = undefined; - var i: usize = 0; - i += fmtBuf(buffer[i..], "panic: "); - i += fmtPanicCause(buffer[i..], cause); - i += fmtBuf(buffer[i..], "\r\n\x00"); - var utf16_buffer: [1000]u16 = undefined; - const len = std.unicode.utf8ToUtf16Le(&utf16_buffer, buffer[0..i]) catch 0; + const len_minus_3 = std.unicode.utf8ToUtf16Le(&utf16_buffer, msg) catch 0; + utf16_buffer[len_minus_3][0..3].* = .{ '\r', '\n', 0 }; + const len = len_minus_3 + 3; const exit_msg = utf16_buffer[0 .. len - 1 :0]; // Output to both std_err and con_out, as std_err is easier @@ -521,15 +512,11 @@ pub fn defaultPanic( }, .cuda, .amdhsa => std.posix.abort(), .plan9 => { - var buffer: [1000]u8 = undefined; - comptime assert(buffer.len > std.os.plan9.ERRMAX); - var i: usize = 0; - i += fmtPanicCause(buffer[i..], cause); - buffer[i] = '\n'; - i += 1; - const len = @min(i, std.os.plan9.ERRMAX - 1); - buffer[len] = 0; - std.os.plan9.exits(buffer[0..len :0]); + var status: [std.os.plan9.ERRMAX]u8 = undefined; + const len = @min(msg.len, status.len - 1); + @memcpy(status[0..len], msg[0..len]); + status[len] = 0; + std.os.plan9.exits(status[0..len :0]); }, else => {}, } @@ -548,26 +535,18 @@ pub fn defaultPanic( _ = panicking.fetchAdd(1, .seq_cst); { - // This code avoids a dependency on formatted printing, the writer interface, - // and limits to only 1 syscall made to print the panic message to stderr. - var buffer: [0x1000]u8 = undefined; - var i: usize = 0; - if (builtin.single_threaded) { - i += fmtBuf(buffer[i..], "panic: "); - } else { - i += fmtBuf(buffer[i..], "thread "); - i += fmtInt10(buffer[i..], std.Thread.getCurrentId()); - i += fmtBuf(buffer[i..], " panic: "); - } - i += fmtPanicCause(buffer[i..], cause); - buffer[i] = '\n'; - i += 1; - const msg = buffer[0..i]; - lockStdErr(); defer unlockStdErr(); - io.getStdErr().writeAll(msg) catch posix.abort(); + const stderr = io.getStdErr().writer(); + if (builtin.single_threaded) { + stderr.print("panic: ", .{}) catch posix.abort(); + } else { + const current_thread_id = std.Thread.getCurrentId(); + stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort(); + } + stderr.print("{s}\n", .{msg}) catch posix.abort(); + if (error_return_trace) |t| dumpStackTrace(t.*); dumpCurrentStackTrace(first_trace_addr orelse @returnAddress()); } @@ -588,108 +567,6 @@ pub fn defaultPanic( posix.abort(); } -pub fn fmtPanicCause(buffer: []u8, cause: std.builtin.PanicCause) usize { - var i: usize = 0; - - switch (cause) { - .reached_unreachable => i += fmtBuf(buffer[i..], "reached unreachable code"), - .unwrap_null => i += fmtBuf(buffer[i..], "attempt to use null value"), - .cast_to_null => i += fmtBuf(buffer[i..], "cast causes pointer to be null"), - .incorrect_alignment => i += fmtBuf(buffer[i..], "incorrect alignment"), - .invalid_error_code => i += fmtBuf(buffer[i..], "invalid error code"), - .cast_truncated_data => i += fmtBuf(buffer[i..], "integer cast truncated bits"), - .negative_to_unsigned => i += fmtBuf(buffer[i..], "attempt to cast negative value to unsigned integer"), - .integer_overflow => i += fmtBuf(buffer[i..], "integer overflow"), - .shl_overflow => i += fmtBuf(buffer[i..], "left shift overflowed bits"), - .shr_overflow => i += fmtBuf(buffer[i..], "right shift overflowed bits"), - .divide_by_zero => i += fmtBuf(buffer[i..], "division by zero"), - .exact_division_remainder => i += fmtBuf(buffer[i..], "exact division produced remainder"), - .inactive_union_field => |info| { - i += fmtBuf(buffer[i..], "access of union field '"); - i += fmtBuf(buffer[i..], info.accessed); - i += fmtBuf(buffer[i..], "' while field '"); - i += fmtBuf(buffer[i..], info.active); - i += fmtBuf(buffer[i..], "' is active"); - }, - .integer_part_out_of_bounds => i += fmtBuf(buffer[i..], "integer part of floating point value out of bounds"), - .corrupt_switch => i += fmtBuf(buffer[i..], "switch on corrupt value"), - .shift_rhs_too_big => i += fmtBuf(buffer[i..], "shift amount is greater than the type size"), - .invalid_enum_value => i += fmtBuf(buffer[i..], "invalid enum value"), - .sentinel_mismatch_usize => |mm| { - i += fmtBuf(buffer[i..], "sentinel mismatch: expected "); - i += fmtInt10(buffer[i..], mm.expected); - i += fmtBuf(buffer[i..], ", found "); - i += fmtInt10(buffer[i..], mm.found); - }, - .sentinel_mismatch_isize => |mm| { - i += fmtBuf(buffer[i..], "sentinel mismatch: expected "); - i += fmtInt10s(buffer[i..], mm.expected); - i += fmtBuf(buffer[i..], ", found "); - i += fmtInt10s(buffer[i..], mm.found); - }, - .sentinel_mismatch_other => i += fmtBuf(buffer[i..], "sentinel mismatch"), - .unwrap_error => |err| { - if (builtin.zig_backend == .stage2_riscv64) { - // https://github.com/ziglang/zig/issues/21519 - i += fmtBuf(buffer[i..], "attempt to unwrap error"); - return i; - } - i += fmtBuf(buffer[i..], "attempt to unwrap error: "); - i += fmtBuf(buffer[i..], @errorName(err)); - }, - .index_out_of_bounds => |oob| { - i += fmtBuf(buffer[i..], "index "); - i += fmtInt10(buffer[i..], oob.index); - i += fmtBuf(buffer[i..], " exceeds length "); - i += fmtInt10(buffer[i..], oob.len); - }, - .start_index_greater_than_end => |oob| { - i += fmtBuf(buffer[i..], "start index "); - i += fmtInt10(buffer[i..], oob.start); - i += fmtBuf(buffer[i..], " exceeds end index "); - i += fmtInt10(buffer[i..], oob.end); - }, - .for_len_mismatch => i += fmtBuf(buffer[i..], "for loop over objects with non-equal lengths"), - .memcpy_len_mismatch => i += fmtBuf(buffer[i..], "@memcpy arguments have non-equal lengths"), - .memcpy_alias => i += fmtBuf(buffer[i..], "@memcpy arguments alias"), - .noreturn_returned => i += fmtBuf(buffer[i..], "'noreturn' function returned"), - .explicit_call => |msg| i += fmtBuf(buffer[i..], msg), - } - - return i; -} - -fn fmtBuf(out_buf: []u8, s: []const u8) usize { - @memcpy(out_buf[0..s.len], s); - return s.len; -} - -fn fmtInt10s(out_buf: []u8, integer_value: isize) usize { - if (integer_value < 0) { - out_buf[0] = '-'; - return 1 + fmtInt10(out_buf[1..], @abs(integer_value)); - } else { - return fmtInt10(out_buf, @abs(integer_value)); - } -} - -fn fmtInt10(out_buf: []u8, integer_value: usize) usize { - var tmp_buf: [50]u8 = undefined; - var i: usize = tmp_buf.len; - var a: usize = integer_value; - - while (true) { - i -= 1; - tmp_buf[i] = '0' + @as(u8, @intCast(a % 10)); - a /= 10; - if (a == 0) break; - } - - const result = tmp_buf[i..]; - @memcpy(out_buf[0..result.len], result); - return result.len; -} - /// Must be called only after adding 1 to `panicking`. There are three callsites. fn waitForOtherThreadToFinishPanicking() void { if (panicking.fetchSub(1, .seq_cst) != 1) { diff --git a/lib/std/debug/FormattedPanic.zig b/lib/std/debug/FormattedPanic.zig new file mode 100644 index 0000000000..0ad6ea696c --- /dev/null +++ b/lib/std/debug/FormattedPanic.zig @@ -0,0 +1,45 @@ +//! This namespace is the default one used by the Zig compiler to emit various +//! kinds of safety panics, due to the logic in `std.builtin.Panic`. +//! +//! Since Zig does not have interfaces, this file serves as an example template +//! for users to provide their own alternative panic handling. +//! +//! As an alternative, see `std.debug.SimplePanic`. + +const std = @import("../std.zig"); + +/// Dumps a stack trace to standard error, then aborts. +/// +/// Explicit calls to `@panic` lower to calling this function. +pub const call: fn ([]const u8, ?*std.builtin.StackTrace, ?usize) noreturn = std.debug.defaultPanic; + +pub fn sentinelMismatch(expected: anytype, found: @TypeOf(expected)) noreturn { + @branchHint(.cold); + std.debug.panicExtra(null, @returnAddress(), "sentinel mismatch: expected {any}, found {any}", .{ + expected, found, + }); +} + +pub fn unwrapError(ert: ?*std.builtin.StackTrace, err: anyerror) noreturn { + @branchHint(.cold); + std.debug.panicExtra(ert, @returnAddress(), "attempt to unwrap error: {s}", .{@errorName(err)}); +} + +pub fn outOfBounds(index: usize, len: usize) noreturn { + @branchHint(.cold); + std.debug.panicExtra(null, @returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len }); +} + +pub fn startGreaterThanEnd(start: usize, end: usize) noreturn { + @branchHint(.cold); + std.debug.panicExtra(null, @returnAddress(), "start index {d} is larger than end index {d}", .{ start, end }); +} + +pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn { + @branchHint(.cold); + std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ + @tagName(accessed), @tagName(active), + }); +} + +pub const messages = std.debug.SimplePanic.messages; diff --git a/lib/std/debug/SimplePanic.zig b/lib/std/debug/SimplePanic.zig new file mode 100644 index 0000000000..b8e818c6fc --- /dev/null +++ b/lib/std/debug/SimplePanic.zig @@ -0,0 +1,86 @@ +//! This namespace is the default one used by the Zig compiler to emit various +//! kinds of safety panics, due to the logic in `std.builtin.Panic`. +//! +//! Since Zig does not have interfaces, this file serves as an example template +//! for users to provide their own alternative panic handling. +//! +//! As an alternative, see `std.debug.FormattedPanic`. + +const std = @import("../std.zig"); + +/// Prints the message to stderr without a newline and then traps. +/// +/// Explicit calls to `@panic` lower to calling this function. +pub fn call(msg: []const u8, ert: ?*std.builtin.StackTrace, ra: ?usize) noreturn { + @branchHint(.cold); + _ = ert; + _ = ra; + std.debug.lockStdErr(); + const stderr = std.io.getStdErr(); + stderr.writeAll(msg) catch {}; + @trap(); +} + +pub fn sentinelMismatch(expected: anytype, found: @TypeOf(expected)) noreturn { + _ = found; + call("sentinel mismatch", null, null); +} + +pub fn unwrapError(ert: ?*std.builtin.StackTrace, err: anyerror) noreturn { + _ = ert; + _ = err; + call("attempt to unwrap error", null, null); +} + +pub fn outOfBounds(index: usize, len: usize) noreturn { + _ = index; + _ = len; + call("index out of bounds", null, null); +} + +pub fn startGreaterThanEnd(start: usize, end: usize) noreturn { + _ = start; + _ = end; + call("start index is larger than end index", null, null); +} + +pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn { + _ = accessed; + call("access of inactive union field", null, null); +} + +pub const messages = struct { + pub const reached_unreachable = "reached unreachable code"; + pub const unwrap_null = "attempt to use null value"; + pub const cast_to_null = "cast causes pointer to be null"; + pub const incorrect_alignment = "incorrect alignment"; + pub const invalid_error_code = "invalid error code"; + pub const cast_truncated_data = "integer cast truncated bits"; + pub const negative_to_unsigned = "attempt to cast negative value to unsigned integer"; + pub const integer_overflow = "integer overflow"; + pub const shl_overflow = "left shift overflowed bits"; + pub const shr_overflow = "right shift overflowed bits"; + pub const divide_by_zero = "division by zero"; + pub const exact_division_remainder = "exact division produced remainder"; + pub const integer_part_out_of_bounds = "integer part of floating point value out of bounds"; + pub const corrupt_switch = "switch on corrupt value"; + pub const shift_rhs_too_big = "shift amount is greater than the type size"; + pub const invalid_enum_value = "invalid enum value"; + pub const for_len_mismatch = "for loop over objects with non-equal lengths"; + pub const memcpy_len_mismatch = "@memcpy arguments have non-equal lengths"; + pub const memcpy_alias = "@memcpy arguments alias"; + pub const noreturn_returned = "'noreturn' function returned"; + + /// To be deleted after zig1.wasm is updated. + pub const inactive_union_field = "access of inactive union field"; + /// To be deleted after zig1.wasm is updated. + pub const sentinel_mismatch = "sentinel mismatch"; + /// To be deleted after zig1.wasm is updated. + pub const unwrap_error = "attempt to unwrap error"; + /// To be deleted after zig1.wasm is updated. + pub const index_out_of_bounds = "index out of bounds"; + /// To be deleted after zig1.wasm is updated. + pub const start_index_greater_than_end = "start index is larger than end index"; + /// To be deleted after zig1.wasm is updated. + pub const unreach = reached_unreachable; +}; diff --git a/src/Sema.zig b/src/Sema.zig index 0a66b04deb..8e36889f92 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2566,7 +2566,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); error_bundle.renderToStdErr(.{ .ttyconf = .no_color }); - crash_report.compilerPanic(.{ .explicit_call = "unexpected compile error occurred" }, null, null); + crash_report.compilerPanic("unexpected compile error occurred", null, null); } if (block) |start_block| { @@ -5810,6 +5810,8 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void const src = block.nodeOffset(inst_data.src_node); const msg_inst = try sema.resolveInst(inst_data.operand); + // `panicWithMsg` would perform this coercion for us, but we can get a better + // source location if we do it here. const coerced_msg = try sema.coerce(block, Type.slice_const_u8, msg_inst, block.builtinCallArgSrc(inst_data.src_node, 0)); if (block.is_comptime) { @@ -5822,7 +5824,7 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void sema.branch_hint = .cold; } - try callPanic(sema, block, src, .explicit_call, coerced_msg, .@"@panic"); + try sema.panicWithMsg(block, src, coerced_msg, .@"@panic"); } fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -7303,33 +7305,6 @@ fn callBuiltin( ); } -const PanicCauseTag = @typeInfo(std.builtin.PanicCause).@"union".tag_type.?; - -fn callPanic( - sema: *Sema, - block: *Block, - call_src: LazySrcLoc, - tag: PanicCauseTag, - payload: Air.Inst.Ref, - call_operation: CallOperation, -) !void { - const pt = sema.pt; - const zcu = pt.zcu; - if (!zcu.backendSupportsFeature(.panic_fn)) { - _ = try block.addNoOp(.trap); - return; - } - const panic_cause_ty = try pt.getBuiltinType("PanicCause"); - const panic_cause = try unionInitFromEnumTag(sema, block, call_src, panic_cause_ty, @intFromEnum(tag), payload); - try preparePanic(sema, block, call_src); - const panic_fn = Air.internedToRef(zcu.panic_func_index); - const err_return_trace = try sema.getErrorReturnTrace(block); - const opt_usize_ty = try pt.optionalType(.usize_type); - const null_usize = try pt.nullValue(opt_usize_ty); - const args: [3]Air.Inst.Ref = .{ panic_cause, err_return_trace, Air.internedToRef(null_usize.toIntern()) }; - try sema.callBuiltin(block, call_src, panic_fn, .auto, &args, call_operation); -} - const CallOperation = enum { call, @"@call", @@ -14233,7 +14208,11 @@ fn maybeErrorUnwrap( .panic => { const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const msg_inst = try sema.resolveInst(inst_data.operand); - try callPanic(sema, block, operand_src, .explicit_call, msg_inst, .@"@panic"); + + const panic_fn = try pt.getBuiltinInnerType("Panic", "call"); + const err_return_trace = try sema.getErrorReturnTrace(block); + const args: [3]Air.Inst.Ref = .{ msg_inst, err_return_trace, .null_value }; + try sema.callBuiltin(block, operand_src, panic_fn, .auto, &args, .@"safety check"); return true; }, else => unreachable, @@ -17380,7 +17359,9 @@ fn analyzeArithmetic( if (block.wantSafety() and want_safety and scalar_tag == .int) { if (zcu.backendSupportsFeature(.safety_checked_instructions)) { - if (air_tag != air_tag_safe) try sema.preparePanicIntegerOverflow(block, src); + if (air_tag != air_tag_safe) { + _ = try sema.preparePanicId(block, src, .integer_overflow); + } return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs); } else { const maybe_op_ov: ?Air.Inst.Tag = switch (air_tag) { @@ -21683,16 +21664,6 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); const src = block.nodeOffset(inst_data.src_node); const operand = try sema.resolveInst(inst_data.operand); - return analyzeTagName(sema, block, src, operand_src, operand); -} - -fn analyzeTagName( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - operand_src: LazySrcLoc, - operand: Air.Inst.Ref, -) CompileError!Air.Inst.Ref { const operand_ty = sema.typeOf(operand); const pt = sema.pt; const zcu = pt.zcu; @@ -27663,7 +27634,7 @@ fn explainWhyTypeIsNotPacked( } } -fn preparePanic(sema: *Sema, block: *Block, src: LazySrcLoc) !void { +fn prepareSimplePanic(sema: *Sema, block: *Block, src: LazySrcLoc) !void { const pt = sema.pt; const zcu = pt.zcu; @@ -27694,30 +27665,33 @@ fn preparePanic(sema: *Sema, block: *Block, src: LazySrcLoc) !void { .val = .none, } }); } - - if (zcu.panic_cause_type == .none) { - const panic_cause_ty = try pt.getBuiltinType("PanicCause"); - try panic_cause_ty.resolveFields(pt); - zcu.panic_cause_type = panic_cause_ty.toIntern(); - zcu.panic_cause_tag_type = panic_cause_ty.unionTagType(zcu).?.toIntern(); - } } -fn preparePanicIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc) !void { +/// Backends depend on panic decls being available when lowering safety-checked +/// instructions. This function ensures the panic function will be available to +/// be called during that time. +fn preparePanicId(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.PanicId) !InternPool.Nav.Index { const pt = sema.pt; const zcu = pt.zcu; - try preparePanic(sema, block, src); - if (zcu.panic_cause_integer_overflow == .none) { - const union_val = try pt.unionValue( - Type.fromInterned(zcu.panic_cause_type), - try pt.enumValueFieldIndex( - Type.fromInterned(zcu.panic_cause_tag_type), - @intFromEnum(PanicCauseTag.integer_overflow), - ), - Value.void, - ); - zcu.panic_cause_integer_overflow = try pt.refValue(union_val.toIntern()); - } + const gpa = sema.gpa; + if (zcu.panic_messages[@intFromEnum(panic_id)].unwrap()) |x| return x; + + try sema.prepareSimplePanic(block, src); + + const panic_messages_ty = try pt.getBuiltinType("panic_messages"); + const msg_nav_index = (sema.namespaceLookup( + block, + LazySrcLoc.unneeded, + panic_messages_ty.getNamespaceIndex(zcu), + try zcu.intern_pool.getOrPutString(gpa, pt.tid, @tagName(panic_id), .no_embedded_nulls), + ) catch |err| switch (err) { + error.AnalysisFail => @panic("std.builtin.panic_messages is corrupt"), + error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable, + error.OutOfMemory => |e| return e, + }).?; + try sema.ensureNavResolved(src, msg_nav_index); + zcu.panic_messages[@intFromEnum(panic_id)] = msg_nav_index.toOptional(); + return msg_nav_index; } fn addSafetyCheck( @@ -27725,7 +27699,7 @@ fn addSafetyCheck( parent_block: *Block, src: LazySrcLoc, ok: Air.Inst.Ref, - panic_cause_tag: PanicCauseTag, + panic_id: Zcu.PanicId, ) !void { const gpa = sema.gpa; assert(!parent_block.is_comptime); @@ -27743,7 +27717,7 @@ fn addSafetyCheck( defer fail_block.instructions.deinit(gpa); - try sema.safetyPanic(&fail_block, src, panic_cause_tag); + try sema.safetyPanic(&fail_block, src, panic_id); try sema.addSafetyCheckExtra(parent_block, ok, &fail_block); } @@ -27812,6 +27786,29 @@ fn addSafetyCheckExtra( parent_block.instructions.appendAssumeCapacity(block_inst); } +fn panicWithMsg(sema: *Sema, block: *Block, src: LazySrcLoc, msg_inst: Air.Inst.Ref, operation: CallOperation) !void { + const pt = sema.pt; + const zcu = pt.zcu; + + if (!zcu.backendSupportsFeature(.panic_fn)) { + _ = try block.addNoOp(.trap); + return; + } + + try sema.prepareSimplePanic(block, src); + + const panic_func = zcu.funcInfo(zcu.panic_func_index); + const panic_fn = try sema.analyzeNavVal(block, src, panic_func.owner_nav); + const null_stack_trace = Air.internedToRef(zcu.null_stack_trace); + + const opt_usize_ty = try pt.optionalType(.usize_type); + const null_ret_addr = Air.internedToRef((try pt.intern(.{ .opt = .{ + .ty = opt_usize_ty.toIntern(), + .val = .none, + } }))); + try sema.callBuiltin(block, src, panic_fn, .auto, &.{ msg_inst, null_stack_trace, null_ret_addr }, operation); +} + fn addSafetyCheckUnwrapError( sema: *Sema, parent_block: *Block, @@ -27849,7 +27846,7 @@ fn safetyPanicUnwrapError(sema: *Sema, block: *Block, src: LazySrcLoc, err: Air. if (!zcu.backendSupportsFeature(.panic_fn)) { _ = try block.addNoOp(.trap); } else { - const panic_fn = try pt.getBuiltin("panicUnwrapError"); + const panic_fn = try pt.getBuiltinInnerType("Panic", "unwrapError"); const err_return_trace = try sema.getErrorReturnTrace(block); const args: [2]Air.Inst.Ref = .{ err_return_trace, err }; try sema.callBuiltin(block, src, panic_fn, .auto, &args, .@"safety check"); @@ -27866,7 +27863,7 @@ fn addSafetyCheckIndexOob( ) !void { assert(!parent_block.is_comptime); const ok = try parent_block.addBinOp(cmp_op, index, len); - return addSafetyCheckCall(sema, parent_block, src, ok, "panicOutOfBounds", &.{ index, len }); + return addSafetyCheckCall(sema, parent_block, src, ok, "outOfBounds", &.{ index, len }); } fn addSafetyCheckInactiveUnionField( @@ -27878,7 +27875,7 @@ fn addSafetyCheckInactiveUnionField( ) !void { assert(!parent_block.is_comptime); const ok = try parent_block.addBinOp(.cmp_eq, active_tag, wanted_tag); - return addSafetyCheckCall(sema, parent_block, src, ok, "panicInactiveUnionField", &.{ active_tag, wanted_tag }); + return addSafetyCheckCall(sema, parent_block, src, ok, "inactiveUnionField", &.{ active_tag, wanted_tag }); } fn addSafetyCheckSentinelMismatch( @@ -27919,7 +27916,7 @@ fn addSafetyCheckSentinelMismatch( break :ok try parent_block.addBinOp(.cmp_eq, expected_sentinel, actual_sentinel); }; - return addSafetyCheckCall(sema, parent_block, src, ok, "panicSentinelMismatch", &.{ + return addSafetyCheckCall(sema, parent_block, src, ok, "sentinelMismatch", &.{ expected_sentinel, actual_sentinel, }); } @@ -27953,7 +27950,7 @@ fn addSafetyCheckCall( if (!zcu.backendSupportsFeature(.panic_fn)) { _ = try fail_block.addNoOp(.trap); } else { - const panic_fn = try pt.getBuiltin(func_name); + const panic_fn = try pt.getBuiltinInnerType("Panic", func_name); try sema.callBuiltin(&fail_block, src, panic_fn, .auto, args, .@"safety check"); } @@ -27961,8 +27958,10 @@ fn addSafetyCheckCall( } /// This does not set `sema.branch_hint`. -fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_cause_tag: PanicCauseTag) CompileError!void { - try callPanic(sema, block, src, panic_cause_tag, .void_value, .@"safety check"); +fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.PanicId) CompileError!void { + const msg_nav_index = try sema.preparePanicId(block, src, panic_id); + const msg_inst = try sema.analyzeNavVal(block, src, msg_nav_index); + try sema.panicWithMsg(block, src, msg_inst, .@"safety check"); } fn emitBackwardBranch(sema: *Sema, block: *Block, src: LazySrcLoc) !void { @@ -33456,7 +33455,7 @@ fn analyzeSlice( assert(!block.is_comptime); try sema.requireRuntimeBlock(block, src, runtime_src.?); const ok = try block.addBinOp(.cmp_lte, start, end); - try sema.addSafetyCheckCall(block, src, ok, "panicStartGreaterThanEnd", &.{ start, end }); + try sema.addSafetyCheckCall(block, src, ok, "startGreaterThanEnd", &.{ start, end }); } const new_len = if (by_length) try sema.coerce(block, Type.usize, uncasted_end_opt, end_src) diff --git a/src/Zcu.zig b/src/Zcu.zig index 34c074d3d4..827c4cca84 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -210,17 +210,40 @@ all_type_references: std.ArrayListUnmanaged(TypeReference) = .empty, /// Freelist of indices in `all_type_references`. free_type_references: std.ArrayListUnmanaged(u32) = .empty, +panic_messages: [PanicId.len]InternPool.Nav.Index.Optional = .{.none} ** PanicId.len, /// The panic function body. panic_func_index: InternPool.Index = .none, null_stack_trace: InternPool.Index = .none, -panic_cause_type: InternPool.Index = .none, -panic_cause_tag_type: InternPool.Index = .none, -panic_cause_integer_overflow: InternPool.Index = .none, generation: u32 = 0, pub const PerThread = @import("Zcu/PerThread.zig"); +pub const PanicId = enum { + reached_unreachable, + unwrap_null, + cast_to_null, + incorrect_alignment, + invalid_error_code, + cast_truncated_data, + negative_to_unsigned, + integer_overflow, + shl_overflow, + shr_overflow, + divide_by_zero, + exact_division_remainder, + integer_part_out_of_bounds, + corrupt_switch, + shift_rhs_too_big, + invalid_enum_value, + for_len_mismatch, + memcpy_len_mismatch, + memcpy_alias, + noreturn_returned, + + pub const len = @typeInfo(PanicId).@"enum".fields.len; +}; + pub const GlobalErrorSet = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void); pub const CImportError = struct { diff --git a/src/crash_report.zig b/src/crash_report.zig index 6f0820b782..99264c8409 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -152,16 +152,12 @@ fn writeFilePath(file: *Zcu.File, writer: anytype) !void { try writer.writeAll(file.sub_file_path); } -pub fn compilerPanic( - cause: std.builtin.PanicCause, - error_return_trace: ?*std.builtin.StackTrace, - maybe_ret_addr: ?usize, -) noreturn { +pub fn compilerPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, maybe_ret_addr: ?usize) noreturn { @branchHint(.cold); PanicSwitch.preDispatch(); const ret_addr = maybe_ret_addr orelse @returnAddress(); const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } }; - PanicSwitch.dispatch(error_return_trace, stack_ctx, cause); + PanicSwitch.dispatch(error_return_trace, stack_ctx, msg); } /// Attaches a global SIGSEGV handler @@ -212,7 +208,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa else => .not_supported, }; - PanicSwitch.dispatch(null, stack_ctx, .{ .explicit_call = error_msg }); + PanicSwitch.dispatch(null, stack_ctx, error_msg); } const WindowsSegfaultMessage = union(enum) { @@ -335,7 +331,7 @@ const PanicSwitch = struct { // it's happening and print a message. var panic_state: *volatile PanicState = &panic_state_raw; if (panic_state.awaiting_dispatch) { - dispatch(null, .{ .current = .{ .ret_addr = null } }, .{ .explicit_call = "Panic while preparing callstack" }); + dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack"); } panic_state.awaiting_dispatch = true; } @@ -355,17 +351,17 @@ const PanicSwitch = struct { pub fn dispatch( trace: ?*const std.builtin.StackTrace, stack_ctx: StackContext, - panic_cause: std.builtin.PanicCause, + msg: []const u8, ) noreturn { var panic_state: *volatile PanicState = &panic_state_raw; debug.assert(panic_state.awaiting_dispatch); panic_state.awaiting_dispatch = false; nosuspend switch (panic_state.recover_stage) { - .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, panic_cause }), - .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, panic_cause }), - .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, panic_cause }), - .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, panic_cause }), - .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, panic_cause }), + .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }), + .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }), + .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }), + .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }), + .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }), .silent_abort => goTo(abort, .{}), }; } @@ -374,7 +370,7 @@ const PanicSwitch = struct { state: *volatile PanicState, trace: ?*const std.builtin.StackTrace, stack: StackContext, - panic_cause: std.builtin.PanicCause, + msg: []const u8, ) noreturn { // use a temporary so there's only one volatile store const new_state = PanicState{ @@ -399,8 +395,6 @@ const PanicSwitch = struct { const current_thread_id = std.Thread.getCurrentId(); stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state}); } - var buffer: [1000]u8 = undefined; - const msg = buffer[0..std.debug.fmtPanicCause(&buffer, panic_cause)]; stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state}); state.recover_stage = .report_stack; @@ -416,9 +410,9 @@ const PanicSwitch = struct { state: *volatile PanicState, trace: ?*const std.builtin.StackTrace, stack: StackContext, - panic_cause: std.builtin.PanicCause, + msg: []const u8, ) noreturn { - recover(state, trace, stack, panic_cause); + recover(state, trace, stack, msg); state.recover_stage = .release_mutex; const stderr = io.getStdErr().writer(); @@ -441,9 +435,9 @@ const PanicSwitch = struct { state: *volatile PanicState, trace: ?*const std.builtin.StackTrace, stack: StackContext, - panic_cause: std.builtin.PanicCause, + msg: []const u8, ) noreturn { - recover(state, trace, stack, panic_cause); + recover(state, trace, stack, msg); goTo(releaseMutex, .{state}); } @@ -459,9 +453,9 @@ const PanicSwitch = struct { state: *volatile PanicState, trace: ?*const std.builtin.StackTrace, stack: StackContext, - panic_cause: std.builtin.PanicCause, + msg: []const u8, ) noreturn { - recover(state, trace, stack, panic_cause); + recover(state, trace, stack, msg); goTo(releaseRefCount, .{state}); } @@ -487,9 +481,9 @@ const PanicSwitch = struct { state: *volatile PanicState, trace: ?*const std.builtin.StackTrace, stack: StackContext, - panic_cause: std.builtin.PanicCause, + msg: []const u8, ) noreturn { - recover(state, trace, stack, panic_cause); + recover(state, trace, stack, msg); state.recover_stage = .silent_abort; const stderr = io.getStdErr().writer(); @@ -513,9 +507,8 @@ const PanicSwitch = struct { state: *volatile PanicState, trace: ?*const std.builtin.StackTrace, stack: StackContext, - panic_cause: std.builtin.PanicCause, + msg: []const u8, ) void { - var buffer: [1000]u8 = undefined; switch (state.recover_verbosity) { .message_and_stack => { // lower the verbosity, and restore it at the end if we don't panic. @@ -523,7 +516,6 @@ const PanicSwitch = struct { const stderr = io.getStdErr().writer(); stderr.writeAll("\nPanicked during a panic: ") catch {}; - const msg = buffer[0..std.debug.fmtPanicCause(&buffer, panic_cause)]; stderr.writeAll(msg) catch {}; stderr.writeAll("\nInner panic stack:\n") catch {}; if (trace) |t| { @@ -538,7 +530,6 @@ const PanicSwitch = struct { const stderr = io.getStdErr().writer(); stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {}; - const msg = buffer[0..std.debug.fmtPanicCause(&buffer, panic_cause)]; stderr.writeAll(msg) catch {}; stderr.writeAll("\n") catch {};