Commit 83991efe10

mlugg <mlugg@mlugg.co.uk>
2025-01-24 03:19:28
compiler: yet more panic handler changes
* `std.builtin.Panic` -> `std.builtin.panic`, because it is a namespace. * `root.Panic` -> `root.panic` for the same reason. There are type checks so that we still allow the legacy `pub fn panic` strategy in the 0.14.0 release. * `std.debug.SimplePanic` -> `std.debug.simple_panic`, same reason. * `std.debug.NoPanic` -> `std.debug.no_panic`, same reason. * `std.debug.FormattedPanic` is now a function `std.debug.FullPanic` which takes as input a `panicFn` and returns a namespace with all the panic functions. This handles the incredibly common case of just wanting to override how the message is printed, whilst keeping nice formatted panics. * Remove `std.builtin.panic.messages`; now, every safety panic has its own function. This reduces binary bloat, as calls to these functions no longer need to prepare any arguments (aside from the error return trace). * Remove some legacy declarations, since a zig1.wasm update has happened. Most of these were related to the panic handler, but a quick grep for "zig1" brought up a couple more results too. Also, add some missing type checks to Sema. Resolves: #22584 formatted -> full
1 parent b3d9b0e
lib/std/debug/FormattedPanic.zig
@@ -1,45 +0,0 @@
-//! 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;
lib/std/debug/no_panic.zig
@@ -0,0 +1,160 @@
+//! This namespace can be used with `pub const panic = std.debug.no_panic;` in the root file.
+//! It emits as little code as possible, for testing purposes.
+//!
+//! For a functional alternative, see `std.debug.FullPanic`.
+
+const std = @import("../std.zig");
+
+pub fn call(_: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn sentinelMismatch(_: anytype, _: anytype) noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn unwrapError(_: ?*std.builtin.StackTrace, _: anyerror) noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn outOfBounds(_: usize, _: usize) noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn startGreaterThanEnd(_: usize, _: usize) noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn inactiveUnionField(_: anytype, _: anytype) noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn reachedUnreachable() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn unwrapNull() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn castToNull() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn incorrectAlignment() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn invalidErrorCode() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn castTruncatedData() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn negativeToUnsigned() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn integerOverflow() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn shlOverflow() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn shrOverflow() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn divideByZero() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn exactDivisionRemainder() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn integerPartOutOfBounds() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn corruptSwitch() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn shiftRhsTooBig() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn invalidEnumValue() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn forLenMismatch() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn memcpyLenMismatch() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn memcpyAlias() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+pub fn noreturnReturned() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
+/// To be deleted after zig1.wasm update.
+pub const messages = struct {
+    pub const reached_unreachable = "";
+    pub const unwrap_null = "";
+    pub const cast_to_null = "";
+    pub const incorrect_alignment = "";
+    pub const invalid_error_code = "";
+    pub const cast_truncated_data = "";
+    pub const negative_to_unsigned = "";
+    pub const integer_overflow = "";
+    pub const shl_overflow = "";
+    pub const shr_overflow = "";
+    pub const divide_by_zero = "";
+    pub const exact_division_remainder = "";
+    pub const integer_part_out_of_bounds = "";
+    pub const corrupt_switch = "";
+    pub const shift_rhs_too_big = "";
+    pub const invalid_enum_value = "";
+    pub const for_len_mismatch = "";
+    pub const memcpy_len_mismatch = "";
+    pub const memcpy_alias = "";
+    pub const noreturn_returned = "";
+};
lib/std/debug/NoPanic.zig
@@ -1,59 +0,0 @@
-//! This namespace can be used with `pub const Panic = std.debug.NoPanic;` in the root file.
-//! It emits as little code as possible, for testing purposes.
-//!
-//! For a functional alternative, see `std.debug.FormattedPanic`.
-
-const std = @import("../std.zig");
-
-pub fn call(_: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
-    @branchHint(.cold);
-    @trap();
-}
-
-pub fn sentinelMismatch(_: anytype, _: anytype) noreturn {
-    @branchHint(.cold);
-    @trap();
-}
-
-pub fn unwrapError(_: ?*std.builtin.StackTrace, _: anyerror) noreturn {
-    @branchHint(.cold);
-    @trap();
-}
-
-pub fn outOfBounds(_: usize, _: usize) noreturn {
-    @branchHint(.cold);
-    @trap();
-}
-
-pub fn startGreaterThanEnd(_: usize, _: usize) noreturn {
-    @branchHint(.cold);
-    @trap();
-}
-
-pub fn inactiveUnionField(_: anytype, _: anytype) noreturn {
-    @branchHint(.cold);
-    @trap();
-}
-
-pub const messages = struct {
-    pub const reached_unreachable = "";
-    pub const unwrap_null = "";
-    pub const cast_to_null = "";
-    pub const incorrect_alignment = "";
-    pub const invalid_error_code = "";
-    pub const cast_truncated_data = "";
-    pub const negative_to_unsigned = "";
-    pub const integer_overflow = "";
-    pub const shl_overflow = "";
-    pub const shr_overflow = "";
-    pub const divide_by_zero = "";
-    pub const exact_division_remainder = "";
-    pub const integer_part_out_of_bounds = "";
-    pub const corrupt_switch = "";
-    pub const shift_rhs_too_big = "";
-    pub const invalid_enum_value = "";
-    pub const for_len_mismatch = "";
-    pub const memcpy_len_mismatch = "";
-    pub const memcpy_alias = "";
-    pub const noreturn_returned = "";
-};
lib/std/debug/SimplePanic.zig → lib/std/debug/simple_panic.zig
@@ -1,10 +1,10 @@
 //! 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`.
+//! 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`.
+//! As an alternative, see `std.debug.FullPanic`.
 
 const std = @import("../std.zig");
 
@@ -49,6 +49,87 @@ pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn {
     call("access of inactive union field", null, null);
 }
 
+pub fn reachedUnreachable() noreturn {
+    call("reached unreachable code", null, null);
+}
+
+pub fn unwrapNull() noreturn {
+    call("attempt to use null value", null, null);
+}
+
+pub fn castToNull() noreturn {
+    call("cast causes pointer to be null", null, null);
+}
+
+pub fn incorrectAlignment() noreturn {
+    call("incorrect alignment", null, null);
+}
+
+pub fn invalidErrorCode() noreturn {
+    call("invalid error code", null, null);
+}
+
+pub fn castTruncatedData() noreturn {
+    call("integer cast truncated bits", null, null);
+}
+
+pub fn negativeToUnsigned() noreturn {
+    call("attempt to cast negative value to unsigned integer", null, null);
+}
+
+pub fn integerOverflow() noreturn {
+    call("integer overflow", null, null);
+}
+
+pub fn shlOverflow() noreturn {
+    call("left shift overflowed bits", null, null);
+}
+
+pub fn shrOverflow() noreturn {
+    call("right shift overflowed bits", null, null);
+}
+
+pub fn divideByZero() noreturn {
+    call("division by zero", null, null);
+}
+
+pub fn exactDivisionRemainder() noreturn {
+    call("exact division produced remainder", null, null);
+}
+
+pub fn integerPartOutOfBounds() noreturn {
+    call("integer part of floating point value out of bounds", null, null);
+}
+
+pub fn corruptSwitch() noreturn {
+    call("switch on corrupt value", null, null);
+}
+
+pub fn shiftRhsTooBig() noreturn {
+    call("shift amount is greater than the type size", null, null);
+}
+
+pub fn invalidEnumValue() noreturn {
+    call("invalid enum value", null, null);
+}
+
+pub fn forLenMismatch() noreturn {
+    call("for loop over objects with non-equal lengths", null, null);
+}
+
+pub fn memcpyLenMismatch() noreturn {
+    call("@memcpy arguments have non-equal lengths", null, null);
+}
+
+pub fn memcpyAlias() noreturn {
+    call("@memcpy arguments alias", null, null);
+}
+
+pub fn noreturnReturned() noreturn {
+    call("'noreturn' function returned", null, null);
+}
+
+/// To be deleted after zig1.wasm update.
 pub const messages = struct {
     pub const reached_unreachable = "reached unreachable code";
     pub const unwrap_null = "attempt to use null value";
@@ -70,17 +151,4 @@ pub const messages = struct {
     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;
 };
lib/std/builtin.zig
@@ -1110,45 +1110,28 @@ pub const TestFn = struct {
 /// Deprecated, use the `Panic` namespace instead.
 /// To be deleted after 0.14.0 is released.
 pub const PanicFn = fn ([]const u8, ?*StackTrace, ?usize) noreturn;
-/// Deprecated, use the `Panic` namespace instead.
-/// To be deleted after 0.14.0 is released.
-pub const panic: PanicFn = Panic.call;
 
 /// 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
+/// 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 (@hasDecl(root, "panic")) // Deprecated, use `Panic` instead.
-    DeprecatedPanic
-else if (builtin.zig_backend == .stage2_riscv64)
-    std.debug.SimplePanic // https://github.com/ziglang/zig/issues/21519
-else
-    std.debug.FormattedPanic;
-
-/// To be deleted after 0.14.0 is released.
-const DeprecatedPanic = struct {
-    pub const call = root.panic;
-    pub const sentinelMismatch = std.debug.FormattedPanic.sentinelMismatch;
-    pub const unwrapError = std.debug.FormattedPanic.unwrapError;
-    pub const outOfBounds = std.debug.FormattedPanic.outOfBounds;
-    pub const startGreaterThanEnd = std.debug.FormattedPanic.startGreaterThanEnd;
-    pub const inactiveUnionField = std.debug.FormattedPanic.inactiveUnionField;
-    pub const messages = std.debug.FormattedPanic.messages;
+pub const panic: type = p: {
+    if (@hasDecl(root, "panic")) {
+        if (@TypeOf(root.panic) != type) {
+            break :p std.debug.FullPanic(root.panic); // Deprecated; make `panic` a namespace instead.
+        }
+        break :p root.panic;
+    }
+    if (@hasDecl(root, "Panic")) {
+        break :p root.Panic; // Deprecated; use `panic` instead.
+    }
+    if (builtin.zig_backend == .stage2_riscv64) {
+        break :p std.debug.simple_panic;
+    }
+    break :p std.debug.FullPanic(std.debug.defaultPanic);
 };
 
 /// 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 const Panic = panic;
 
 pub noinline fn returnError() void {
     @branchHint(.unlikely);
lib/std/debug.zig
@@ -21,9 +21,124 @@ 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");
-pub const NoPanic = @import("debug/NoPanic.zig");
+pub const simple_panic = @import("debug/simple_panic.zig");
+pub const no_panic = @import("debug/no_panic.zig");
+
+/// A fully-featured panic handler namespace which lowers all panics to calls to `panicFn`.
+/// Safety panics will use formatted printing to provide a meaningful error message.
+/// The signature of `panicFn` should match that of `defaultPanic`.
+pub fn FullPanic(comptime panicFn: fn ([]const u8, ?*std.builtin.StackTrace, ?usize) noreturn) type {
+    return struct {
+        pub const call = panicFn;
+        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 fn reachedUnreachable() noreturn {
+            @branchHint(.cold);
+            call("reached unreachable code", null, @returnAddress());
+        }
+        pub fn unwrapNull() noreturn {
+            @branchHint(.cold);
+            call("attempt to use null value", null, @returnAddress());
+        }
+        pub fn castToNull() noreturn {
+            @branchHint(.cold);
+            call("cast causes pointer to be null", null, @returnAddress());
+        }
+        pub fn incorrectAlignment() noreturn {
+            @branchHint(.cold);
+            call("incorrect alignment", null, @returnAddress());
+        }
+        pub fn invalidErrorCode() noreturn {
+            @branchHint(.cold);
+            call("invalid error code", null, @returnAddress());
+        }
+        pub fn castTruncatedData() noreturn {
+            @branchHint(.cold);
+            call("integer cast truncated bits", null, @returnAddress());
+        }
+        pub fn negativeToUnsigned() noreturn {
+            @branchHint(.cold);
+            call("attempt to cast negative value to unsigned integer", null, @returnAddress());
+        }
+        pub fn integerOverflow() noreturn {
+            @branchHint(.cold);
+            call("integer overflow", null, @returnAddress());
+        }
+        pub fn shlOverflow() noreturn {
+            @branchHint(.cold);
+            call("left shift overflowed bits", null, @returnAddress());
+        }
+        pub fn shrOverflow() noreturn {
+            @branchHint(.cold);
+            call("right shift overflowed bits", null, @returnAddress());
+        }
+        pub fn divideByZero() noreturn {
+            @branchHint(.cold);
+            call("division by zero", null, @returnAddress());
+        }
+        pub fn exactDivisionRemainder() noreturn {
+            @branchHint(.cold);
+            call("exact division produced remainder", null, @returnAddress());
+        }
+        pub fn integerPartOutOfBounds() noreturn {
+            @branchHint(.cold);
+            call("integer part of floating point value out of bounds", null, @returnAddress());
+        }
+        pub fn corruptSwitch() noreturn {
+            @branchHint(.cold);
+            call("switch on corrupt value", null, @returnAddress());
+        }
+        pub fn shiftRhsTooBig() noreturn {
+            @branchHint(.cold);
+            call("shift amount is greater than the type size", null, @returnAddress());
+        }
+        pub fn invalidEnumValue() noreturn {
+            @branchHint(.cold);
+            call("invalid enum value", null, @returnAddress());
+        }
+        pub fn forLenMismatch() noreturn {
+            @branchHint(.cold);
+            call("for loop over objects with non-equal lengths", null, @returnAddress());
+        }
+        pub fn memcpyLenMismatch() noreturn {
+            @branchHint(.cold);
+            call("@memcpy arguments have non-equal lengths", null, @returnAddress());
+        }
+        pub fn memcpyAlias() noreturn {
+            @branchHint(.cold);
+            call("@memcpy arguments alias", null, @returnAddress());
+        }
+        pub fn noreturnReturned() noreturn {
+            @branchHint(.cold);
+            call("'noreturn' function returned", null, @returnAddress());
+        }
+
+        /// To be deleted after zig1.wasm update.
+        pub const messages = simple_panic.messages;
+    };
+}
 
 /// Unresolved source locations can be represented with a single `usize` that
 /// corresponds to a virtual memory address of the program counter. Combined
@@ -441,7 +556,7 @@ pub fn panicExtra(
             break :blk &buf;
         },
     };
-    std.builtin.Panic.call(msg, trace, ret_addr);
+    std.builtin.panic.call(msg, trace, ret_addr);
 }
 
 /// Non-zero whenever the program triggered a panic.
lib/std/meta.zig
@@ -448,8 +448,7 @@ pub fn fieldNames(comptime T: type) *const [fields(T).len][:0]const u8 {
     return comptime blk: {
         const fieldInfos = fields(T);
         var names: [fieldInfos.len][:0]const u8 = undefined;
-        // This concat can be removed with the next zig1 update.
-        for (&names, fieldInfos) |*name, field| name.* = field.name ++ "";
+        for (&names, fieldInfos) |*name, field| name.* = field.name;
         const final = names;
         break :blk &final;
     };
lib/std/Target.zig
@@ -370,7 +370,7 @@ pub const Os = struct {
         range: std.SemanticVersion.Range,
         glibc: std.SemanticVersion,
         /// Android API level.
-        android: u32 = 14, // This default value is to be deleted after zig1.wasm is updated.
+        android: u32,
 
         pub inline fn includesVersion(range: LinuxVersionRange, ver: std.SemanticVersion) bool {
             return range.range.includesVersion(ver);
src/codegen/llvm.zig
@@ -5019,18 +5019,6 @@ pub const FuncGen = struct {
         );
     }
 
-    fn resolveNullOptUsize(self: *FuncGen) Error!Builder.Constant {
-        const o = self.ng.object;
-        const pt = o.pt;
-        if (o.null_opt_usize == .no_init) {
-            o.null_opt_usize = try self.resolveValue(Value.fromInterned(try pt.intern(.{ .opt = .{
-                .ty = try pt.intern(.{ .opt_type = .usize_type }),
-                .val = .none,
-            } })));
-        }
-        return o.null_opt_usize;
-    }
-
     fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.CoveragePoint) Error!void {
         const o = self.ng.object;
         const zcu = o.pt.zcu;
@@ -5732,30 +5720,14 @@ pub const FuncGen = struct {
         }
     }
 
-    fn buildSimplePanic(fg: *FuncGen, panic_id: Zcu.PanicId) !void {
+    fn buildSimplePanic(fg: *FuncGen, panic_id: Zcu.SimplePanicId) !void {
         const o = fg.ng.object;
         const zcu = o.pt.zcu;
-        const ip = &zcu.intern_pool;
-        const msg_len: u64, const msg_ptr: Builder.Constant = msg: {
-            const str_val = zcu.builtin_decl_values.get(panic_id.toBuiltin());
-            assert(str_val != .none);
-            const slice = ip.indexToKey(str_val).slice;
-            break :msg .{ Value.fromInterned(slice.len).toUnsignedInt(zcu), try o.lowerValue(slice.ptr) };
-        };
-        const null_opt_addr_global = try fg.resolveNullOptUsize();
         const target = zcu.getTarget();
-        const llvm_usize = try o.lowerType(Type.usize);
-        // example:
-        // call fastcc void @test2.panic(
-        //   ptr @builtin.panic_messages.integer_overflow__anon_987, ; msg.ptr
-        //   i64 16,                                                 ; msg.len
-        //   ptr null,                                               ; stack trace
-        //   ptr @2,                                                 ; addr (null ?usize)
-        // )
-        const panic_func = zcu.funcInfo(zcu.builtin_decl_values.get(.@"Panic.call"));
-        const panic_nav = ip.getNav(panic_func.owner_nav);
-        const fn_info = zcu.typeToFunc(Type.fromInterned(panic_nav.typeOf(ip))).?;
+        const panic_func = zcu.funcInfo(zcu.builtin_decl_values.get(panic_id.toBuiltin()));
+        const fn_info = zcu.typeToFunc(.fromInterned(panic_func.ty)).?;
         const panic_global = try o.resolveLlvmFunction(panic_func.owner_nav);
+
         const has_err_trace = zcu.comp.config.any_error_tracing and fn_info.cc == .auto;
         if (has_err_trace) assert(fg.err_ret_trace != .none);
         _ = try fg.wip.callIntrinsicAssumeCold();
@@ -5765,18 +5737,7 @@ pub const FuncGen = struct {
             .none,
             panic_global.typeOf(&o.builder),
             panic_global.toValue(&o.builder),
-            if (has_err_trace) &.{
-                fg.err_ret_trace,
-                msg_ptr.toValue(),
-                try o.builder.intValue(llvm_usize, msg_len),
-                try o.builder.nullValue(.ptr),
-                null_opt_addr_global.toValue(),
-            } else &.{
-                msg_ptr.toValue(),
-                try o.builder.intValue(llvm_usize, msg_len),
-                try o.builder.nullValue(.ptr),
-                null_opt_addr_global.toValue(),
-            },
+            if (has_err_trace) &.{fg.err_ret_trace} else &.{},
             "",
         );
         _ = try fg.wip.@"unreachable"();
src/Zcu/PerThread.zig
@@ -605,7 +605,7 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
         // We use an arbitrary element to check if the state has been resolved yet.
         const to_check: Zcu.BuiltinDecl = switch (stage) {
             .main => .Type,
-            .panic => .Panic,
+            .panic => .panic,
             .va_list => .VaList,
         };
         if (zcu.builtin_decl_values.get(to_check) != .none) return;
src/crash_report.zig
@@ -18,18 +18,12 @@ const dev = @import("dev.zig");
 /// To use these crash report diagnostics, publish this panic in your main file
 /// and add `pub const enable_segfault_handler = false;` to your `std_options`.
 /// You will also need to call initialize() on startup, preferably as the very first operation in your program.
-pub const Panic = if (build_options.enable_debug_extensions) struct {
-    pub const call = compilerPanic;
-    pub const sentinelMismatch = std.debug.FormattedPanic.sentinelMismatch;
-    pub const unwrapError = std.debug.FormattedPanic.unwrapError;
-    pub const outOfBounds = std.debug.FormattedPanic.outOfBounds;
-    pub const startGreaterThanEnd = std.debug.FormattedPanic.startGreaterThanEnd;
-    pub const inactiveUnionField = std.debug.FormattedPanic.inactiveUnionField;
-    pub const messages = std.debug.FormattedPanic.messages;
-} else if (dev.env == .bootstrap)
-    std.debug.SimplePanic
+pub const panic = if (build_options.enable_debug_extensions)
+    std.debug.FullPanic(compilerPanic)
+else if (dev.env == .bootstrap)
+    std.debug.simple_panic
 else
-    std.debug.FormattedPanic;
+    std.debug.FullPanic(std.debug.defaultPanic);
 
 /// Install signal handlers to identify crashes and report diagnostics.
 pub fn initialize() void {
src/main.zig
@@ -56,7 +56,7 @@ pub const std_options: std.Options = .{
     },
 };
 
-pub const Panic = crash_report.Panic;
+pub const panic = crash_report.panic;
 
 var wasi_preopens: fs.wasi.Preopens = undefined;
 pub fn wasi_cwd() std.os.wasi.fd_t {
src/Sema.zig
@@ -5918,13 +5918,14 @@ fn zirCompileLog(
 }
 
 fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
     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));
+    const coerced_msg = try sema.coerce(block, .slice_const_u8, msg_inst, block.builtinCallArgSrc(inst_data.src_node, 0));
 
     if (block.isComptime()) {
         return sema.fail(block, src, "encountered @panic at comptime", .{});
@@ -5936,7 +5937,23 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
         sema.branch_hint = .cold;
     }
 
-    try sema.panicWithMsg(block, src, coerced_msg, .@"@panic");
+    if (!zcu.backendSupportsFeature(.panic_fn)) {
+        _ = try block.addNoOp(.trap);
+        return;
+    }
+
+    try sema.ensureMemoizedStateResolved(src, .panic);
+    try zcu.ensureFuncBodyAnalysisQueued(zcu.builtin_decl_values.get(.@"panic.call"));
+
+    const panic_fn = Air.internedToRef(zcu.builtin_decl_values.get(.@"panic.call"));
+    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, &.{ coerced_msg, null_stack_trace, null_ret_addr }, .@"@panic");
 }
 
 fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
@@ -13787,7 +13804,7 @@ fn maybeErrorUnwrap(
                 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
                 const msg_inst = try sema.resolveInst(inst_data.operand);
 
-                const panic_fn = try getBuiltin(sema, operand_src, .@"Panic.call");
+                const panic_fn = try getBuiltin(sema, operand_src, .@"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, Air.internedToRef(panic_fn), .auto, &args, .@"safety check");
@@ -27083,15 +27100,16 @@ fn explainWhyTypeIsNotPacked(
 /// 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, src: LazySrcLoc, panic_id: Zcu.PanicId) !InternPool.Index {
+fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
     const zcu = sema.pt.zcu;
     try sema.ensureMemoizedStateResolved(src, .panic);
-    try zcu.ensureFuncBodyAnalysisQueued(zcu.builtin_decl_values.get(.@"Panic.call"));
+    const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin());
+    try zcu.ensureFuncBodyAnalysisQueued(panic_func);
     switch (sema.owner.unwrap()) {
         .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
         .func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(owner_func, true),
     }
-    return zcu.builtin_decl_values.get(panic_id.toBuiltin());
+    return panic_func;
 }
 
 fn addSafetyCheck(
@@ -27099,7 +27117,7 @@ fn addSafetyCheck(
     parent_block: *Block,
     src: LazySrcLoc,
     ok: Air.Inst.Ref,
-    panic_id: Zcu.PanicId,
+    panic_id: Zcu.SimplePanicId,
 ) !void {
     const gpa = sema.gpa;
     assert(!parent_block.isComptime());
@@ -27186,29 +27204,6 @@ 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.ensureMemoizedStateResolved(src, .panic);
-    try zcu.ensureFuncBodyAnalysisQueued(zcu.builtin_decl_values.get(.@"Panic.call"));
-
-    const panic_fn = Air.internedToRef(zcu.builtin_decl_values.get(.@"Panic.call"));
-    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,
@@ -27246,7 +27241,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 getBuiltin(sema, src, .@"Panic.unwrapError");
+        const panic_fn = try getBuiltin(sema, src, .@"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, Air.internedToRef(panic_fn), .auto, &args, .@"safety check");
@@ -27263,7 +27258,7 @@ fn addSafetyCheckIndexOob(
 ) !void {
     assert(!parent_block.isComptime());
     const ok = try parent_block.addBinOp(cmp_op, index, len);
-    return addSafetyCheckCall(sema, parent_block, src, ok, .@"Panic.outOfBounds", &.{ index, len });
+    return addSafetyCheckCall(sema, parent_block, src, ok, .@"panic.outOfBounds", &.{ index, len });
 }
 
 fn addSafetyCheckInactiveUnionField(
@@ -27275,7 +27270,7 @@ fn addSafetyCheckInactiveUnionField(
 ) !void {
     assert(!parent_block.isComptime());
     const ok = try parent_block.addBinOp(.cmp_eq, active_tag, wanted_tag);
-    return addSafetyCheckCall(sema, parent_block, src, ok, .@"Panic.inactiveUnionField", &.{ active_tag, wanted_tag });
+    return addSafetyCheckCall(sema, parent_block, src, ok, .@"panic.inactiveUnionField", &.{ active_tag, wanted_tag });
 }
 
 fn addSafetyCheckSentinelMismatch(
@@ -27316,7 +27311,7 @@ fn addSafetyCheckSentinelMismatch(
         break :ok try parent_block.addBinOp(.cmp_eq, expected_sentinel, actual_sentinel);
     };
 
-    return addSafetyCheckCall(sema, parent_block, src, ok, .@"Panic.sentinelMismatch", &.{
+    return addSafetyCheckCall(sema, parent_block, src, ok, .@"panic.sentinelMismatch", &.{
         expected_sentinel, actual_sentinel,
     });
 }
@@ -27358,9 +27353,13 @@ fn addSafetyCheckCall(
 }
 
 /// This does not set `sema.branch_hint`.
-fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.PanicId) CompileError!void {
-    const msg_val = try sema.preparePanicId(src, panic_id);
-    try sema.panicWithMsg(block, src, Air.internedToRef(msg_val), .@"safety check");
+fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) CompileError!void {
+    if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
+        _ = try block.addNoOp(.trap);
+    } else {
+        const panic_fn = try sema.preparePanicId(src, panic_id);
+        try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");
+    }
 }
 
 fn emitBackwardBranch(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
@@ -32818,7 +32817,7 @@ fn analyzeSlice(
         assert(!block.isComptime());
         try sema.requireRuntimeBlock(block, src, runtime_src.?);
         const ok = try block.addBinOp(.cmp_lte, start, end);
-        try sema.addSafetyCheckCall(block, src, ok, .@"Panic.startGreaterThanEnd", &.{ start, end });
+        try sema.addSafetyCheckCall(block, src, ok, .@"panic.startGreaterThanEnd", &.{ start, end });
     }
     const new_len = if (by_length)
         try sema.coerce(block, Type.usize, uncasted_end_opt, end_src)
@@ -38525,14 +38524,9 @@ pub fn analyzeMemoizedState(sema: *Sema, block: *Block, simple_src: LazySrcLoc,
                     break :val uncoerced_val;
                 },
                 .func => val: {
-                    if (try sema.getExpectedBuiltinFnType(src, builtin_decl)) |func_ty| {
-                        const coerced = try sema.coerce(block, func_ty, Air.internedToRef(uncoerced_val.toIntern()), src);
-                        break :val .fromInterned(coerced.toInterned().?);
-                    }
-                    if (uncoerced_val.typeOf(zcu).zigTypeTag(zcu) != .@"fn") {
-                        return sema.fail(block, src, "{s}.{s} is not a function", .{ parent_name, name });
-                    }
-                    break :val uncoerced_val;
+                    const func_ty = try sema.getExpectedBuiltinFnType(src, builtin_decl);
+                    const coerced = try sema.coerce(block, func_ty, Air.internedToRef(uncoerced_val.toIntern()), src);
+                    break :val .fromInterned(coerced.toInterned().?);
                 },
                 .string => val: {
                     const coerced = try sema.coerce(block, .slice_const_u8, Air.internedToRef(uncoerced_val.toIntern()), src);
@@ -38567,16 +38561,19 @@ pub fn analyzeMemoizedState(sema: *Sema, block: *Block, simple_src: LazySrcLoc,
     return any_changed;
 }
 
-/// Given that `decl.kind() == .func`, get the type expected of the function if necessary.
-/// If this will be type checked by `Sema` anyway, this function may return `null`. In
-/// particular, generic functions should return `null`, as `Sema` will necessarily check
-/// them at instantiation time. Returning non-null is necessary only when backends can emit
-/// calls to the function, as is the case with the panic handler.
-fn getExpectedBuiltinFnType(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl) CompileError!?Type {
+/// Given that `decl.kind() == .func`, get the type expected of the function.
+fn getExpectedBuiltinFnType(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl) CompileError!Type {
     const pt = sema.pt;
     return switch (decl) {
+        // `noinline fn () void`
+        .returnError => try pt.funcType(.{
+            .param_types = &.{},
+            .return_type = .void_type,
+            .is_noinline = true,
+        }),
+
         // `fn ([]const u8, ?*StackTrace, ?usize) noreturn`
-        .@"Panic.call" => try pt.funcType(.{
+        .@"panic.call" => try pt.funcType(.{
             .param_types = &.{
                 .slice_const_u8_type,
                 (try pt.optionalType(
@@ -38589,8 +38586,17 @@ fn getExpectedBuiltinFnType(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl)
             .return_type = .noreturn_type,
         }),
 
+        // `fn (anytype, anytype) noreturn`
+        .@"panic.sentinelMismatch",
+        .@"panic.inactiveUnionField",
+        => try pt.funcType(.{
+            .param_types = &.{ .generic_poison_type, .generic_poison_type },
+            .return_type = .noreturn_type,
+            .is_generic = true,
+        }),
+
         // `fn (?*StackTrace, anyerror) noreturn`
-        .@"Panic.unwrapError" => try pt.funcType(.{
+        .@"panic.unwrapError" => try pt.funcType(.{
             .param_types = &.{
                 (try pt.optionalType(
                     (try pt.singleMutPtrType(
@@ -38603,21 +38609,38 @@ fn getExpectedBuiltinFnType(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl)
         }),
 
         // `fn (usize, usize) noreturn`
-        .@"Panic.outOfBounds",
-        .@"Panic.startGreaterThanEnd",
+        .@"panic.outOfBounds",
+        .@"panic.startGreaterThanEnd",
         => try pt.funcType(.{
             .param_types = &.{ .usize_type, .usize_type },
             .return_type = .noreturn_type,
         }),
 
-        // Generic functions, so calls are necessarily validated by Sema
-        .@"Panic.sentinelMismatch",
-        .@"Panic.inactiveUnionField",
-        => null,
-
-        // Other functions called exclusively by Sema
-        .returnError,
-        => null,
+        // `fn () noreturn`
+        .@"panic.reachedUnreachable",
+        .@"panic.unwrapNull",
+        .@"panic.castToNull",
+        .@"panic.incorrectAlignment",
+        .@"panic.invalidErrorCode",
+        .@"panic.castTruncatedData",
+        .@"panic.negativeToUnsigned",
+        .@"panic.integerOverflow",
+        .@"panic.shlOverflow",
+        .@"panic.shrOverflow",
+        .@"panic.divideByZero",
+        .@"panic.exactDivisionRemainder",
+        .@"panic.integerPartOutOfBounds",
+        .@"panic.corruptSwitch",
+        .@"panic.shiftRhsTooBig",
+        .@"panic.invalidEnumValue",
+        .@"panic.forLenMismatch",
+        .@"panic.memcpyLenMismatch",
+        .@"panic.memcpyAlias",
+        .@"panic.noreturnReturned",
+        => try pt.funcType(.{
+            .param_types = &.{},
+            .return_type = .noreturn_type,
+        }),
 
         else => unreachable,
     };
src/Zcu.zig
@@ -269,34 +269,33 @@ pub const BuiltinDecl = enum {
     @"Type.Opaque",
     @"Type.Declaration",
 
-    Panic,
-    @"Panic.call",
-    @"Panic.sentinelMismatch",
-    @"Panic.unwrapError",
-    @"Panic.outOfBounds",
-    @"Panic.startGreaterThanEnd",
-    @"Panic.inactiveUnionField",
-    @"Panic.messages",
-    @"Panic.messages.reached_unreachable",
-    @"Panic.messages.unwrap_null",
-    @"Panic.messages.cast_to_null",
-    @"Panic.messages.incorrect_alignment",
-    @"Panic.messages.invalid_error_code",
-    @"Panic.messages.cast_truncated_data",
-    @"Panic.messages.negative_to_unsigned",
-    @"Panic.messages.integer_overflow",
-    @"Panic.messages.shl_overflow",
-    @"Panic.messages.shr_overflow",
-    @"Panic.messages.divide_by_zero",
-    @"Panic.messages.exact_division_remainder",
-    @"Panic.messages.integer_part_out_of_bounds",
-    @"Panic.messages.corrupt_switch",
-    @"Panic.messages.shift_rhs_too_big",
-    @"Panic.messages.invalid_enum_value",
-    @"Panic.messages.for_len_mismatch",
-    @"Panic.messages.memcpy_len_mismatch",
-    @"Panic.messages.memcpy_alias",
-    @"Panic.messages.noreturn_returned",
+    panic,
+    @"panic.call",
+    @"panic.sentinelMismatch",
+    @"panic.unwrapError",
+    @"panic.outOfBounds",
+    @"panic.startGreaterThanEnd",
+    @"panic.inactiveUnionField",
+    @"panic.reachedUnreachable",
+    @"panic.unwrapNull",
+    @"panic.castToNull",
+    @"panic.incorrectAlignment",
+    @"panic.invalidErrorCode",
+    @"panic.castTruncatedData",
+    @"panic.negativeToUnsigned",
+    @"panic.integerOverflow",
+    @"panic.shlOverflow",
+    @"panic.shrOverflow",
+    @"panic.divideByZero",
+    @"panic.exactDivisionRemainder",
+    @"panic.integerPartOutOfBounds",
+    @"panic.corruptSwitch",
+    @"panic.shiftRhsTooBig",
+    @"panic.invalidEnumValue",
+    @"panic.forLenMismatch",
+    @"panic.memcpyLenMismatch",
+    @"panic.memcpyAlias",
+    @"panic.noreturnReturned",
 
     VaList,
 
@@ -345,39 +344,35 @@ pub const BuiltinDecl = enum {
             .@"Type.Declaration",
             => .type,
 
-            .Panic => .type,
-
-            .@"Panic.call",
-            .@"Panic.sentinelMismatch",
-            .@"Panic.unwrapError",
-            .@"Panic.outOfBounds",
-            .@"Panic.startGreaterThanEnd",
-            .@"Panic.inactiveUnionField",
+            .panic => .type,
+
+            .@"panic.call",
+            .@"panic.sentinelMismatch",
+            .@"panic.unwrapError",
+            .@"panic.outOfBounds",
+            .@"panic.startGreaterThanEnd",
+            .@"panic.inactiveUnionField",
+            .@"panic.reachedUnreachable",
+            .@"panic.unwrapNull",
+            .@"panic.castToNull",
+            .@"panic.incorrectAlignment",
+            .@"panic.invalidErrorCode",
+            .@"panic.castTruncatedData",
+            .@"panic.negativeToUnsigned",
+            .@"panic.integerOverflow",
+            .@"panic.shlOverflow",
+            .@"panic.shrOverflow",
+            .@"panic.divideByZero",
+            .@"panic.exactDivisionRemainder",
+            .@"panic.integerPartOutOfBounds",
+            .@"panic.corruptSwitch",
+            .@"panic.shiftRhsTooBig",
+            .@"panic.invalidEnumValue",
+            .@"panic.forLenMismatch",
+            .@"panic.memcpyLenMismatch",
+            .@"panic.memcpyAlias",
+            .@"panic.noreturnReturned",
             => .func,
-
-            .@"Panic.messages" => .type,
-
-            .@"Panic.messages.reached_unreachable",
-            .@"Panic.messages.unwrap_null",
-            .@"Panic.messages.cast_to_null",
-            .@"Panic.messages.incorrect_alignment",
-            .@"Panic.messages.invalid_error_code",
-            .@"Panic.messages.cast_truncated_data",
-            .@"Panic.messages.negative_to_unsigned",
-            .@"Panic.messages.integer_overflow",
-            .@"Panic.messages.shl_overflow",
-            .@"Panic.messages.shr_overflow",
-            .@"Panic.messages.divide_by_zero",
-            .@"Panic.messages.exact_division_remainder",
-            .@"Panic.messages.integer_part_out_of_bounds",
-            .@"Panic.messages.corrupt_switch",
-            .@"Panic.messages.shift_rhs_too_big",
-            .@"Panic.messages.invalid_enum_value",
-            .@"Panic.messages.for_len_mismatch",
-            .@"Panic.messages.memcpy_len_mismatch",
-            .@"Panic.messages.memcpy_alias",
-            .@"Panic.messages.noreturn_returned",
-            => .string,
         };
     }
 
@@ -423,7 +418,7 @@ pub const BuiltinDecl = enum {
     const Memoized = std.enums.EnumArray(BuiltinDecl, InternPool.Index);
 };
 
-pub const PanicId = enum {
+pub const SimplePanicId = enum {
     reached_unreachable,
     unwrap_null,
     cast_to_null,
@@ -445,19 +440,31 @@ pub const PanicId = enum {
     memcpy_alias,
     noreturn_returned,
 
-    pub fn toBuiltin(id: PanicId) BuiltinDecl {
-        const first_msg: PanicId = @enumFromInt(0);
-        const first_decl = @field(BuiltinDecl, "Panic.messages." ++ @tagName(first_msg));
-        comptime {
-            // Ensure that the messages are ordered the same in `BuiltinDecl` as they are here.
-            for (@typeInfo(PanicId).@"enum".fields) |panic_field| {
-                const expect_name = "Panic.messages." ++ panic_field.name;
-                const expect_idx = @intFromEnum(first_decl) + panic_field.value;
-                const actual_idx = @intFromEnum(@field(BuiltinDecl, expect_name));
-                assert(expect_idx == actual_idx);
-            }
-        }
-        return @enumFromInt(@intFromEnum(first_decl) + @intFromEnum(id));
+    pub fn toBuiltin(id: SimplePanicId) BuiltinDecl {
+        return switch (id) {
+            // zig fmt: off
+            .reached_unreachable        => .@"panic.reachedUnreachable",
+            .unwrap_null                => .@"panic.unwrapNull",
+            .cast_to_null               => .@"panic.castToNull",
+            .incorrect_alignment        => .@"panic.incorrectAlignment",
+            .invalid_error_code         => .@"panic.invalidErrorCode",
+            .cast_truncated_data        => .@"panic.castTruncatedData",
+            .negative_to_unsigned       => .@"panic.negativeToUnsigned",
+            .integer_overflow           => .@"panic.integerOverflow",
+            .shl_overflow               => .@"panic.shlOverflow",
+            .shr_overflow               => .@"panic.shrOverflow",
+            .divide_by_zero             => .@"panic.divideByZero",
+            .exact_division_remainder   => .@"panic.exactDivisionRemainder",
+            .integer_part_out_of_bounds => .@"panic.integerPartOutOfBounds",
+            .corrupt_switch             => .@"panic.corruptSwitch",
+            .shift_rhs_too_big          => .@"panic.shiftRhsTooBig",
+            .invalid_enum_value         => .@"panic.invalidEnumValue",
+            .for_len_mismatch           => .@"panic.forLenMismatch",
+            .memcpy_len_mismatch        => .@"panic.memcpyLenMismatch",
+            .memcpy_alias               => .@"panic.memcpyAlias",
+            .noreturn_returned          => .@"panic.noreturnReturned",
+            // zig fmt: on
+        };
     }
 };
 
test/cases/compile_errors/bad_panic_call_signature.zig
@@ -0,0 +1,46 @@
+const simple_panic = std.debug.simple_panic;
+pub const panic = struct {
+    pub fn call(msg: []const u8, bad1: usize, bad2: void) noreturn {
+        _ = msg;
+        _ = bad1;
+        _ = bad2;
+        @trap();
+    }
+    pub const sentinelMismatch = simple_panic.sentinelMismatch;
+    pub const unwrapError = simple_panic.unwrapError;
+    pub const outOfBounds = simple_panic.outOfBounds;
+    pub const startGreaterThanEnd = simple_panic.startGreaterThanEnd;
+    pub const inactiveUnionField = simple_panic.inactiveUnionField;
+    pub const reachedUnreachable = simple_panic.reachedUnreachable;
+    pub const unwrapNull = simple_panic.unwrapNull;
+    pub const castToNull = simple_panic.castToNull;
+    pub const incorrectAlignment = simple_panic.incorrectAlignment;
+    pub const invalidErrorCode = simple_panic.invalidErrorCode;
+    pub const castTruncatedData = simple_panic.castTruncatedData;
+    pub const negativeToUnsigned = simple_panic.negativeToUnsigned;
+    pub const integerOverflow = simple_panic.integerOverflow;
+    pub const shlOverflow = simple_panic.shlOverflow;
+    pub const shrOverflow = simple_panic.shrOverflow;
+    pub const divideByZero = simple_panic.divideByZero;
+    pub const exactDivisionRemainder = simple_panic.exactDivisionRemainder;
+    pub const integerPartOutOfBounds = simple_panic.integerPartOutOfBounds;
+    pub const corruptSwitch = simple_panic.corruptSwitch;
+    pub const shiftRhsTooBig = simple_panic.shiftRhsTooBig;
+    pub const invalidEnumValue = simple_panic.invalidEnumValue;
+    pub const forLenMismatch = simple_panic.forLenMismatch;
+    pub const memcpyLenMismatch = simple_panic.memcpyLenMismatch;
+    pub const memcpyAlias = simple_panic.memcpyAlias;
+    pub const noreturnReturned = simple_panic.noreturnReturned;
+};
+
+export fn foo(a: u8) void {
+    @setRuntimeSafety(true);
+    _ = a + 1; // safety check to reference the panic handler
+}
+
+const std = @import("std");
+
+// error
+//
+// :3:9: error: expected type 'fn ([]const u8, ?*builtin.StackTrace, ?usize) noreturn', found 'fn ([]const u8, usize, void) noreturn'
+// :3:9: note: parameter 1 'usize' cannot cast into '?*builtin.StackTrace'
test/cases/compile_errors/bad_panic_generic_signature.zig
@@ -0,0 +1,41 @@
+const simple_panic = std.debug.simple_panic;
+pub const panic = struct {
+    pub fn sentinelMismatch() void {} // invalid
+    pub const call = simple_panic.call;
+    pub const unwrapError = simple_panic.unwrapError;
+    pub const outOfBounds = simple_panic.outOfBounds;
+    pub const startGreaterThanEnd = simple_panic.startGreaterThanEnd;
+    pub const inactiveUnionField = simple_panic.inactiveUnionField;
+    pub const reachedUnreachable = simple_panic.reachedUnreachable;
+    pub const unwrapNull = simple_panic.unwrapNull;
+    pub const castToNull = simple_panic.castToNull;
+    pub const incorrectAlignment = simple_panic.incorrectAlignment;
+    pub const invalidErrorCode = simple_panic.invalidErrorCode;
+    pub const castTruncatedData = simple_panic.castTruncatedData;
+    pub const negativeToUnsigned = simple_panic.negativeToUnsigned;
+    pub const integerOverflow = simple_panic.integerOverflow;
+    pub const shlOverflow = simple_panic.shlOverflow;
+    pub const shrOverflow = simple_panic.shrOverflow;
+    pub const divideByZero = simple_panic.divideByZero;
+    pub const exactDivisionRemainder = simple_panic.exactDivisionRemainder;
+    pub const integerPartOutOfBounds = simple_panic.integerPartOutOfBounds;
+    pub const corruptSwitch = simple_panic.corruptSwitch;
+    pub const shiftRhsTooBig = simple_panic.shiftRhsTooBig;
+    pub const invalidEnumValue = simple_panic.invalidEnumValue;
+    pub const forLenMismatch = simple_panic.forLenMismatch;
+    pub const memcpyLenMismatch = simple_panic.memcpyLenMismatch;
+    pub const memcpyAlias = simple_panic.memcpyAlias;
+    pub const noreturnReturned = simple_panic.noreturnReturned;
+};
+
+export fn foo(arr: *const [2]u8) void {
+    @setRuntimeSafety(true);
+    _ = arr[0..1 :0];
+}
+
+const std = @import("std");
+
+// error
+//
+// :3:9: error: expected type 'fn (anytype, anytype) noreturn', found 'fn () void'
+// :3:9: note: non-generic function cannot cast into a generic function
test/cases/compile_errors/bad_panic_signature.zig
@@ -1,28 +0,0 @@
-pub const Panic = struct {
-    pub const call = badPanicSignature;
-    pub const sentinelMismatch = std.debug.FormattedPanic.sentinelMismatch;
-    pub const unwrapError = std.debug.FormattedPanic.unwrapError;
-    pub const outOfBounds = std.debug.FormattedPanic.outOfBounds;
-    pub const startGreaterThanEnd = std.debug.FormattedPanic.startGreaterThanEnd;
-    pub const inactiveUnionField = std.debug.FormattedPanic.inactiveUnionField;
-    pub const messages = std.debug.FormattedPanic.messages;
-};
-
-fn badPanicSignature(msg: []const u8, bad1: usize, bad2: void) noreturn {
-    _ = msg;
-    _ = bad1;
-    _ = bad2;
-    @trap();
-}
-
-export fn foo(a: u8) void {
-    @setRuntimeSafety(true);
-    _ = a + 1; // safety check to reference the panic handler
-}
-
-const std = @import("std");
-
-// error
-//
-// :2:9: error: expected type 'fn ([]const u8, ?*builtin.StackTrace, ?usize) noreturn', found 'fn ([]const u8, usize, void) noreturn'
-// :2:9: note: parameter 1 'usize' cannot cast into '?*builtin.StackTrace'
test/incremental/change_panic_handler
@@ -9,15 +9,7 @@ pub fn main() !u8 {
     _ = a + 1;
     return 1;
 }
-pub const Panic = struct {
-    pub const call = myPanic;
-    pub const sentinelMismatch = std.debug.FormattedPanic.sentinelMismatch;
-    pub const unwrapError = std.debug.FormattedPanic.unwrapError;
-    pub const outOfBounds = std.debug.FormattedPanic.outOfBounds;
-    pub const startGreaterThanEnd = std.debug.FormattedPanic.startGreaterThanEnd;
-    pub const inactiveUnionField = std.debug.FormattedPanic.inactiveUnionField;
-    pub const messages = std.debug.FormattedPanic.messages;
-};
+pub const panic = std.debug.FullPanic(myPanic);
 fn myPanic(msg: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     std.io.getStdOut().writer().print("panic message: {s}\n", .{msg}) catch {};
     std.process.exit(0);
@@ -33,15 +25,7 @@ pub fn main() !u8 {
     _ = a + 1;
     return 1;
 }
-pub const Panic = struct {
-    pub const call = myPanic;
-    pub const sentinelMismatch = std.debug.FormattedPanic.sentinelMismatch;
-    pub const unwrapError = std.debug.FormattedPanic.unwrapError;
-    pub const outOfBounds = std.debug.FormattedPanic.outOfBounds;
-    pub const startGreaterThanEnd = std.debug.FormattedPanic.startGreaterThanEnd;
-    pub const inactiveUnionField = std.debug.FormattedPanic.inactiveUnionField;
-    pub const messages = std.debug.FormattedPanic.messages;
-};
+pub const panic = std.debug.FullPanic(myPanic);
 fn myPanic(msg: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     std.io.getStdOut().writer().print("new panic message: {s}\n", .{msg}) catch {};
     std.process.exit(0);
@@ -57,15 +41,7 @@ pub fn main() !u8 {
     _ = a + 1;
     return 1;
 }
-pub const Panic = struct {
-    pub const call = myPanicNew;
-    pub const sentinelMismatch = std.debug.FormattedPanic.sentinelMismatch;
-    pub const unwrapError = std.debug.FormattedPanic.unwrapError;
-    pub const outOfBounds = std.debug.FormattedPanic.outOfBounds;
-    pub const startGreaterThanEnd = std.debug.FormattedPanic.startGreaterThanEnd;
-    pub const inactiveUnionField = std.debug.FormattedPanic.inactiveUnionField;
-    pub const messages = std.debug.FormattedPanic.messages;
-};
+pub const panic = std.debug.FullPanic(myPanicNew);
 fn myPanicNew(msg: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     std.io.getStdOut().writer().print("third panic message: {s}\n", .{msg}) catch {};
     std.process.exit(0);
test/incremental/change_panic_handler_explicit
@@ -0,0 +1,141 @@
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
+#target=x86_64-windows-cbe
+#update=initial version
+#file=main.zig
+pub fn main() !u8 {
+    var a: u8 = undefined;
+    a = 255;
+    _ = a + 1;
+    return 1;
+}
+const no_panic = std.debug.no_panic;
+pub const panic = struct {
+    pub const call = myPanic;
+    pub fn integerOverflow() noreturn {
+        @panic("integer overflow");
+    }
+    pub const sentinelMismatch = no_panic.sentinelMismatch;
+    pub const unwrapError = no_panic.unwrapError;
+    pub const outOfBounds = no_panic.outOfBounds;
+    pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
+    pub const inactiveUnionField = no_panic.inactiveUnionField;
+    pub const reachedUnreachable = no_panic.reachedUnreachable;
+    pub const unwrapNull = no_panic.unwrapNull;
+    pub const castToNull = no_panic.castToNull;
+    pub const incorrectAlignment = no_panic.incorrectAlignment;
+    pub const invalidErrorCode = no_panic.invalidErrorCode;
+    pub const castTruncatedData = no_panic.castTruncatedData;
+    pub const negativeToUnsigned = no_panic.negativeToUnsigned;
+    pub const shlOverflow = no_panic.shlOverflow;
+    pub const shrOverflow = no_panic.shrOverflow;
+    pub const divideByZero = no_panic.divideByZero;
+    pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
+    pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
+    pub const corruptSwitch = no_panic.corruptSwitch;
+    pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
+    pub const invalidEnumValue = no_panic.invalidEnumValue;
+    pub const forLenMismatch = no_panic.forLenMismatch;
+    pub const memcpyLenMismatch = no_panic.memcpyLenMismatch;
+    pub const memcpyAlias = no_panic.memcpyAlias;
+    pub const noreturnReturned = no_panic.noreturnReturned;
+};
+fn myPanic(msg: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+    std.io.getStdOut().writer().print("panic message: {s}\n", .{msg}) catch {};
+    std.process.exit(0);
+}
+const std = @import("std");
+#expect_stdout="panic message: integer overflow\n"
+
+#update=change the panic handler body
+#file=main.zig
+pub fn main() !u8 {
+    var a: u8 = undefined;
+    a = 255;
+    _ = a + 1;
+    return 1;
+}
+const no_panic = std.debug.no_panic;
+pub const panic = struct {
+    pub const call = myPanic;
+    pub fn integerOverflow() noreturn {
+        @panic("integer overflow");
+    }
+    pub const sentinelMismatch = no_panic.sentinelMismatch;
+    pub const unwrapError = no_panic.unwrapError;
+    pub const outOfBounds = no_panic.outOfBounds;
+    pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
+    pub const inactiveUnionField = no_panic.inactiveUnionField;
+    pub const reachedUnreachable = no_panic.reachedUnreachable;
+    pub const unwrapNull = no_panic.unwrapNull;
+    pub const castToNull = no_panic.castToNull;
+    pub const incorrectAlignment = no_panic.incorrectAlignment;
+    pub const invalidErrorCode = no_panic.invalidErrorCode;
+    pub const castTruncatedData = no_panic.castTruncatedData;
+    pub const negativeToUnsigned = no_panic.negativeToUnsigned;
+    pub const shlOverflow = no_panic.shlOverflow;
+    pub const shrOverflow = no_panic.shrOverflow;
+    pub const divideByZero = no_panic.divideByZero;
+    pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
+    pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
+    pub const corruptSwitch = no_panic.corruptSwitch;
+    pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
+    pub const invalidEnumValue = no_panic.invalidEnumValue;
+    pub const forLenMismatch = no_panic.forLenMismatch;
+    pub const memcpyLenMismatch = no_panic.memcpyLenMismatch;
+    pub const memcpyAlias = no_panic.memcpyAlias;
+    pub const noreturnReturned = no_panic.noreturnReturned;
+};
+fn myPanic(msg: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+    std.io.getStdOut().writer().print("new panic message: {s}\n", .{msg}) catch {};
+    std.process.exit(0);
+}
+const std = @import("std");
+#expect_stdout="new panic message: integer overflow\n"
+
+#update=change the panic handler function value
+#file=main.zig
+pub fn main() !u8 {
+    var a: u8 = undefined;
+    a = 255;
+    _ = a + 1;
+    return 1;
+}
+const no_panic = std.debug.no_panic;
+pub const panic = struct {
+    pub const call = myPanicNew;
+    pub fn integerOverflow() noreturn {
+        @panic("integer overflow");
+    }
+    pub const sentinelMismatch = std.debug.no_panic.sentinelMismatch;
+    pub const unwrapError = std.debug.no_panic.unwrapError;
+    pub const outOfBounds = std.debug.no_panic.outOfBounds;
+    pub const startGreaterThanEnd = std.debug.no_panic.startGreaterThanEnd;
+    pub const inactiveUnionField = std.debug.no_panic.inactiveUnionField;
+    pub const messages = std.debug.no_panic.messages;
+    pub const reachedUnreachable = no_panic.reachedUnreachable;
+    pub const unwrapNull = no_panic.unwrapNull;
+    pub const castToNull = no_panic.castToNull;
+    pub const incorrectAlignment = no_panic.incorrectAlignment;
+    pub const invalidErrorCode = no_panic.invalidErrorCode;
+    pub const castTruncatedData = no_panic.castTruncatedData;
+    pub const negativeToUnsigned = no_panic.negativeToUnsigned;
+    pub const shlOverflow = no_panic.shlOverflow;
+    pub const shrOverflow = no_panic.shrOverflow;
+    pub const divideByZero = no_panic.divideByZero;
+    pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
+    pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
+    pub const corruptSwitch = no_panic.corruptSwitch;
+    pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
+    pub const invalidEnumValue = no_panic.invalidEnumValue;
+    pub const forLenMismatch = no_panic.forLenMismatch;
+    pub const memcpyLenMismatch = no_panic.memcpyLenMismatch;
+    pub const memcpyAlias = no_panic.memcpyAlias;
+    pub const noreturnReturned = no_panic.noreturnReturned;
+};
+fn myPanicNew(msg: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+    std.io.getStdOut().writer().print("third panic message: {s}\n", .{msg}) catch {};
+    std.process.exit(0);
+}
+const std = @import("std");
+#expect_stdout="third panic message: integer overflow\n"