Commit 4f8d244e7e

Andrew Kelley <andrew@ziglang.org>
2024-09-25 20:11:48
remove formatted panics
implements #17969
1 parent 04e694a
lib/compiler_rt/common.zig
@@ -77,11 +77,9 @@ 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(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
-    _ = error_return_trace;
+pub fn panic(cause: std.builtin.PanicCause, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
     if (builtin.is_test) {
-        @branchHint(.cold);
-        std.debug.panic("{s}", .{msg});
+        std.debug.defaultPanic(cause, error_return_trace, ret_addr orelse @returnAddress());
     } else {
         unreachable;
     }
lib/std/builtin.zig
@@ -763,181 +763,66 @@ pub const TestFn = struct {
 
 /// 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 ([]const u8, ?*StackTrace, ?usize) noreturn;
+pub const PanicFn = fn (PanicCause, ?*StackTrace, ?usize) noreturn;
 
-/// This function is used by the Zig language code generation and
-/// therefore must be kept in sync with the compiler implementation.
+/// The entry point for auto-generated calls by the compiler.
 pub const panic: PanicFn = if (@hasDecl(root, "panic"))
     root.panic
 else if (@hasDecl(root, "os") and @hasDecl(root.os, "panic"))
     root.os.panic
 else
-    default_panic;
+    std.debug.defaultPanic;
 
-/// This function is used by the Zig language code generation and
+/// This data structure is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
-pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr: ?usize) noreturn {
-    @branchHint(.cold);
-
-    // For backends that cannot handle the language features depended on by the
-    // default panic handler, we have a simpler panic handler:
-    if (builtin.zig_backend == .stage2_wasm or
-        builtin.zig_backend == .stage2_arm or
-        builtin.zig_backend == .stage2_aarch64 or
-        builtin.zig_backend == .stage2_x86 or
-        (builtin.zig_backend == .stage2_x86_64 and (builtin.target.ofmt != .elf and builtin.target.ofmt != .macho)) or
-        builtin.zig_backend == .stage2_sparc64 or
-        builtin.zig_backend == .stage2_spirv64)
-    {
-        while (true) {
-            @breakpoint();
-        }
-    }
-
-    if (builtin.zig_backend == .stage2_riscv64) {
-        std.debug.print("panic: {s}\n", .{msg});
-        @breakpoint();
-        std.posix.exit(127);
-    }
-
-    switch (builtin.os.tag) {
-        .freestanding => {
-            while (true) {
-                @breakpoint();
-            }
-        },
-        .wasi => {
-            std.debug.print("{s}", .{msg});
-            std.posix.abort();
-        },
-        .uefi => {
-            const uefi = std.os.uefi;
-
-            const Formatter = struct {
-                pub fn fmt(exit_msg: []const u8, out: []u16) ![:0]u16 {
-                    var u8_buf: [256]u8 = undefined;
-                    const slice = try std.fmt.bufPrint(&u8_buf, "err: {s}\r\n", .{exit_msg});
-                    // We pass len - 1 because we need to add a null terminator after
-                    const len = try std.unicode.utf8ToUtf16Le(out[0 .. out.len - 1], slice);
-
-                    out[len] = 0;
-
-                    return out[0..len :0];
-                }
-            };
-
-            const ExitData = struct {
-                pub fn create_exit_data(exit_msg: [:0]u16, exit_size: *usize) ![*:0]u16 {
-                    // Need boot services for pool allocation
-                    if (uefi.system_table.boot_services == null) {
-                        return error.BootServicesUnavailable;
-                    }
-
-                    // ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220)
-                    const exit_data: []u16 = try uefi.raw_pool_allocator.alloc(u16, exit_msg.len + 1);
-
-                    @memcpy(exit_data[0 .. exit_msg.len + 1], exit_msg[0 .. exit_msg.len + 1]);
-                    exit_size.* = exit_msg.len + 1;
-
-                    return @as([*:0]u16, @ptrCast(exit_data.ptr));
-                }
-            };
-
-            var buf: [256]u16 = undefined;
-            const utf16 = Formatter.fmt(msg, &buf) catch null;
-
-            var exit_size: usize = 0;
-            const exit_data = if (utf16) |u|
-                ExitData.create_exit_data(u, &exit_size) catch null
-            else
-                null;
-
-            if (utf16) |str| {
-                // Output to both std_err and con_out, as std_err is easier
-                // to read in stuff like QEMU at times, but, unlike con_out,
-                // isn't visible on actual hardware if directly booted into
-                inline for ([_]?*uefi.protocol.SimpleTextOutput{ uefi.system_table.std_err, uefi.system_table.con_out }) |o| {
-                    if (o) |out| {
-                        _ = out.setAttribute(uefi.protocol.SimpleTextOutput.red);
-                        _ = out.outputString(str);
-                        _ = out.setAttribute(uefi.protocol.SimpleTextOutput.white);
-                    }
-                }
-            }
-
-            if (uefi.system_table.boot_services) |bs| {
-                _ = bs.exit(uefi.handle, .Aborted, exit_size, exit_data);
-            }
-
-            // Didn't have boot_services, just fallback to whatever.
-            std.posix.abort();
-        },
-        .cuda, .amdhsa => std.posix.abort(),
-        .plan9 => {
-            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 => {
-            const first_trace_addr = ret_addr orelse @returnAddress();
-            std.debug.panicImpl(error_return_trace, first_trace_addr, msg);
-        },
-    }
-}
-
-pub fn panicSentinelMismatch(expected: anytype, actual: @TypeOf(expected)) noreturn {
-    @branchHint(.cold);
-    std.debug.panicExtra(null, @returnAddress(), "sentinel mismatch: expected {any}, found {any}", .{ expected, actual });
-}
-
-pub fn panicUnwrapError(st: ?*StackTrace, err: anyerror) noreturn {
-    @branchHint(.cold);
-    std.debug.panicExtra(st, @returnAddress(), "attempt to unwrap error: {s}", .{@errorName(err)});
-}
-
-pub fn panicOutOfBounds(index: usize, len: usize) noreturn {
-    @branchHint(.cold);
-    std.debug.panicExtra(null, @returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len });
-}
+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,
+
+    pub const IndexOutOfBounds = struct {
+        index: usize,
+        len: usize,
+    };
 
-pub fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn {
-    @branchHint(.cold);
-    std.debug.panicExtra(null, @returnAddress(), "start index {d} is larger than end index {d}", .{ start, end });
-}
+    pub const StartIndexGreaterThanEnd = struct {
+        start: usize,
+        end: usize,
+    };
 
-pub fn panicInactiveUnionField(active: anytype, wanted: @TypeOf(active)) noreturn {
-    @branchHint(.cold);
-    std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ @tagName(wanted), @tagName(active) });
-}
+    pub const SentinelMismatchUsize = struct {
+        expected: usize,
+        found: usize,
+    };
 
-pub const panic_messages = struct {
-    pub const unreach = "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 inactive_union_field = "access of inactive union field";
-    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 sentinel_mismatch = "sentinel mismatch";
-    pub const unwrap_error = "attempt to unwrap error";
-    pub const index_out_of_bounds = "index out of bounds";
-    pub const start_index_greater_than_end = "start index is larger than end index";
-    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";
+    pub const InactiveUnionField = struct {
+        active: []const u8,
+        accessed: []const u8,
+    };
 };
 
 pub noinline fn returnError(st: *StackTrace) void {
lib/std/debug.zig
@@ -408,14 +408,15 @@ pub fn assertReadable(slice: []const volatile u8) void {
     for (slice) |*byte| _ = byte.*;
 }
 
+/// Equivalent to `@panic` but with a formatted message.
 pub fn panic(comptime format: []const u8, args: anytype) noreturn {
     @branchHint(.cold);
 
     panicExtra(@errorReturnTrace(), @returnAddress(), format, args);
 }
 
-/// `panicExtra` is useful when you want to print out an `@errorReturnTrace`
-/// and also print out some values.
+/// Equivalent to `@panic` but with a formatted message, and with an explicitly
+/// provided `@errorReturnTrace` and return address.
 pub fn panicExtra(
     trace: ?*std.builtin.StackTrace,
     ret_addr: ?usize,
@@ -447,11 +448,104 @@ 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;
 
-// `panicImpl` could be useful in implementing a custom panic handler which
-// calls the default handler (on supported platforms)
-pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize, msg: []const u8) noreturn {
+// Dumps a stack trace to standard error, then aborts.
+//
+// This function avoids a dependency on formatted printing.
+pub fn defaultPanic(
+    cause: std.builtin.PanicCause,
+    trace: ?*const std.builtin.StackTrace,
+    first_trace_addr: ?usize,
+) noreturn {
     @branchHint(.cold);
 
+    // For backends that cannot handle the language features depended on by the
+    // default panic handler, we have a simpler panic handler:
+    if (builtin.zig_backend == .stage2_wasm or
+        builtin.zig_backend == .stage2_arm or
+        builtin.zig_backend == .stage2_aarch64 or
+        builtin.zig_backend == .stage2_x86 or
+        (builtin.zig_backend == .stage2_x86_64 and (builtin.target.ofmt != .elf and builtin.target.ofmt != .macho)) or
+        builtin.zig_backend == .stage2_sparc64 or
+        builtin.zig_backend == .stage2_spirv64)
+    {
+        @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();
+        },
+        .wasi => {
+            // TODO: before merging my branch, unify this logic with the main panic logic
+            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();
+        },
+        .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 exit_msg = utf16_buffer[0 .. len - 1 :0];
+
+            // Output to both std_err and con_out, as std_err is easier
+            // to read in stuff like QEMU at times, but, unlike con_out,
+            // isn't visible on actual hardware if directly booted into
+            inline for ([_]?*uefi.protocol.SimpleTextOutput{ uefi.system_table.std_err, uefi.system_table.con_out }) |o| {
+                if (o) |out| {
+                    _ = out.setAttribute(uefi.protocol.SimpleTextOutput.red);
+                    _ = out.outputString(exit_msg);
+                    _ = out.setAttribute(uefi.protocol.SimpleTextOutput.white);
+                }
+            }
+
+            if (uefi.system_table.boot_services) |bs| {
+                // ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220)
+                const exit_data: []u16 = uefi.raw_pool_allocator.alloc(u16, exit_msg.len + 1) catch @trap();
+                @memcpy(exit_data, exit_msg[0..exit_data.len]); // Includes null terminator.
+                _ = bs.exit(uefi.handle, .Aborted, exit_msg.len + 1, exit_data);
+            }
+            @trap();
+        },
+        .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]);
+        },
+        else => {},
+    }
+
     if (enable_segfault_handler) {
         // If a segfault happens while panicking, we want it to actually segfault, not trigger
         // the handler.
@@ -465,23 +559,29 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
 
             _ = panicking.fetchAdd(1, .seq_cst);
 
-            // Make sure to release the mutex when done
             {
-                lockStdErr();
-                defer unlockStdErr();
-
-                const stderr = io.getStdErr().writer();
+                // 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) {
-                    stderr.print("panic: ", .{}) catch posix.abort();
+                    i += fmtBuf(buffer[i..], "panic: ");
                 } 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 (trace) |t| {
-                    dumpStackTrace(t.*);
+                    i += fmtBuf(buffer[i..], "thread ");
+                    i += fmtInt10(buffer[i..], std.Thread.getCurrentId());
+                    i += fmtBuf(buffer[i..], " panic: ");
                 }
-                dumpCurrentStackTrace(first_trace_addr);
+                i += fmtPanicCause(&buffer, cause);
+                buffer[i] = '\n';
+                i += 1;
+                const msg = buffer[0..i];
+
+                lockStdErr();
+                defer unlockStdErr();
+
+                io.getStdErr().writeAll(msg) catch posix.abort();
+                if (trace) |t| dumpStackTrace(t.*);
+                dumpCurrentStackTrace(first_trace_addr orelse @returnAddress());
             }
 
             waitForOtherThreadToFinishPanicking();
@@ -489,20 +589,99 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
         1 => {
             panic_stage = 2;
 
-            // A panic happened while trying to print a previous panic message,
-            // we're still holding the mutex but that's fine as we're going to
-            // call abort()
-            const stderr = io.getStdErr().writer();
-            stderr.print("Panicked during a panic. Aborting.\n", .{}) catch posix.abort();
-        },
-        else => {
-            // Panicked while printing "Panicked during a panic."
+            // A panic happened while trying to print a previous panic message.
+            // We're still holding the mutex but that's fine as we're going to
+            // call abort().
+            io.getStdErr().writeAll("aborting due to recursive panic\n") catch {};
         },
+        else => {}, // Panicked while printing the recursive panic message.
     };
 
     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_other => i += fmtBuf(buffer[i..], "sentinel mismatch"),
+        .unwrap_error => |err| {
+            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 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' + (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) {
@@ -1157,7 +1336,7 @@ pub const default_enable_segfault_handler = runtime_safety and have_segfault_han
 
 pub fn maybeEnableSegfaultHandler() void {
     if (enable_segfault_handler) {
-        std.debug.attachSegfaultHandler();
+        attachSegfaultHandler();
     }
 }
 
@@ -1289,46 +1468,29 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WIN
     }
 }
 
-fn handleSegfaultWindowsExtra(
-    info: *windows.EXCEPTION_POINTERS,
-    msg: u8,
-    label: ?[]const u8,
-) noreturn {
-    const exception_address = @intFromPtr(info.ExceptionRecord.ExceptionAddress);
-    if (windows.CONTEXT != void) {
-        nosuspend switch (panic_stage) {
-            0 => {
-                panic_stage = 1;
-                _ = panicking.fetchAdd(1, .seq_cst);
-
-                {
-                    lockStdErr();
-                    defer unlockStdErr();
-
-                    dumpSegfaultInfoWindows(info, msg, label);
-                }
+fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn {
+    comptime assert(windows.CONTEXT != void);
+    nosuspend switch (panic_stage) {
+        0 => {
+            panic_stage = 1;
+            _ = panicking.fetchAdd(1, .seq_cst);
+
+            {
+                lockStdErr();
+                defer unlockStdErr();
 
-                waitForOtherThreadToFinishPanicking();
-            },
-            else => {
-                // panic mutex already locked
                 dumpSegfaultInfoWindows(info, msg, label);
-            },
-        };
-        posix.abort();
-    } else {
-        switch (msg) {
-            0 => panicImpl(null, exception_address, "{s}", label.?),
-            1 => {
-                const format_item = "Segmentation fault at address 0x{x}";
-                var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large
-                const to_print = std.fmt.bufPrint(buf[0..buf.len], format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable;
-                panicImpl(null, exception_address, to_print);
-            },
-            2 => panicImpl(null, exception_address, "Illegal Instruction"),
-            else => unreachable,
-        }
-    }
+            }
+
+            waitForOtherThreadToFinishPanicking();
+        },
+        1 => {
+            panic_stage = 2;
+            io.getStdErr().writeAll("aborting due to recursive panic\n") catch {};
+        },
+        else => {},
+    };
+    posix.abort();
 }
 
 fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
@@ -1347,7 +1509,7 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {
     const sp = asm (""
         : [argc] "={rsp}" (-> usize),
     );
-    std.debug.print("{s} sp = 0x{x}\n", .{ prefix, sp });
+    print("{s} sp = 0x{x}\n", .{ prefix, sp });
 }
 
 test "manage resources correctly" {
lib/std/fmt.zig
@@ -1197,7 +1197,7 @@ pub fn formatInt(
     if (base == 10) {
         while (a >= 100) : (a = @divTrunc(a, 100)) {
             index -= 2;
-            buf[index..][0..2].* = digits2(@as(usize, @intCast(a % 100)));
+            buf[index..][0..2].* = digits2(@intCast(a % 100));
         }
 
         if (a < 10) {
@@ -1205,13 +1205,13 @@ pub fn formatInt(
             buf[index] = '0' + @as(u8, @intCast(a));
         } else {
             index -= 2;
-            buf[index..][0..2].* = digits2(@as(usize, @intCast(a)));
+            buf[index..][0..2].* = digits2(@intCast(a));
         }
     } else {
         while (true) {
             const digit = a % base;
             index -= 1;
-            buf[index] = digitToChar(@as(u8, @intCast(digit)), case);
+            buf[index] = digitToChar(@intCast(digit), case);
             a /= base;
             if (a == 0) break;
         }
@@ -1242,11 +1242,7 @@ pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, case: Case, options
 
 // Converts values in the range [0, 100) to a string.
 pub fn digits2(value: usize) [2]u8 {
-    return ("0001020304050607080910111213141516171819" ++
-        "2021222324252627282930313233343536373839" ++
-        "4041424344454647484950515253545556575859" ++
-        "6061626364656667686970717273747576777879" ++
-        "8081828384858687888990919293949596979899")[value * 2 ..][0..2].*;
+    return "00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899"[value * 2 ..][0..2].*;
 }
 
 const FormatDurationData = struct {
src/Sema/bitcast.zig
@@ -613,11 +613,11 @@ const PackValueBits = struct {
                         pack.bit_offset = prev_bit_offset;
                         break :backing;
                     }
-                    return Value.fromInterned(try pt.intern(.{ .un = .{
+                    return Value.fromInterned(try pt.internUnion(.{
                         .ty = ty.toIntern(),
                         .tag = .none,
                         .val = backing_val.toIntern(),
-                    } }));
+                    }));
                 }
 
                 const field_order = try pack.arena.alloc(u32, ty.unionTagTypeHypothetical(zcu).enumFieldCount(zcu));
@@ -658,21 +658,21 @@ const PackValueBits = struct {
                         continue;
                     }
                     const tag_val = try pt.enumValueFieldIndex(ty.unionTagTypeHypothetical(zcu), field_idx);
-                    return Value.fromInterned(try pt.intern(.{ .un = .{
+                    return Value.fromInterned(try pt.internUnion(.{
                         .ty = ty.toIntern(),
                         .tag = tag_val.toIntern(),
                         .val = field_val.toIntern(),
-                    } }));
+                    }));
                 }
 
                 // No field could represent the value. Just do whatever happens when we try to read
                 // the backing type - either `undefined` or `error.ReinterpretDeclRef`.
                 const backing_val = try pack.get(backing_ty);
-                return Value.fromInterned(try pt.intern(.{ .un = .{
+                return Value.fromInterned(try pt.internUnion(.{
                     .ty = ty.toIntern(),
                     .tag = .none,
                     .val = backing_val.toIntern(),
-                } }));
+                }));
             },
             else => return pack.primitive(ty),
         }
src/Zcu/PerThread.zig
@@ -2697,11 +2697,16 @@ pub fn reportRetryableFileError(
     gop.value_ptr.* = err_msg;
 }
 
-///Shortcut for calling `intern_pool.get`.
+/// Shortcut for calling `intern_pool.get`.
 pub fn intern(pt: Zcu.PerThread, key: InternPool.Key) Allocator.Error!InternPool.Index {
     return pt.zcu.intern_pool.get(pt.zcu.gpa, pt.tid, key);
 }
 
+/// Shortcut for calling `intern_pool.getUnion`.
+pub fn internUnion(pt: Zcu.PerThread, un: InternPool.Key.Union) Allocator.Error!InternPool.Index {
+    return pt.zcu.intern_pool.getUnion(pt.zcu.gpa, pt.tid, un);
+}
+
 /// Essentially a shortcut for calling `intern_pool.getCoerced`.
 /// However, this function also allows coercing `extern`s. The `InternPool` function can't do
 /// this because it requires potentially pushing to the job queue.
@@ -2949,11 +2954,12 @@ pub fn intValue_i64(pt: Zcu.PerThread, ty: Type, x: i64) Allocator.Error!Value {
 }
 
 pub fn unionValue(pt: Zcu.PerThread, union_ty: Type, tag: Value, val: Value) Allocator.Error!Value {
-    return Value.fromInterned(try pt.intern(.{ .un = .{
+    const zcu = pt.zcu;
+    return Value.fromInterned(try zcu.intern_pool.getUnion(zcu.gpa, pt.tid, .{
         .ty = union_ty.toIntern(),
         .tag = tag.toIntern(),
         .val = val.toIntern(),
-    } }));
+    }));
 }
 
 /// This function casts the float representation down to the representation of the type, potentially
src/Compilation.zig
@@ -195,7 +195,6 @@ job_queued_compiler_rt_obj: bool = false,
 job_queued_fuzzer_lib: bool = false,
 job_queued_update_builtin_zig: bool,
 alloc_failure_occurred: bool = false,
-formatted_panics: bool = false,
 last_update_was_cache_hit: bool = false,
 
 c_source_files: []const CSourceFile,
@@ -1088,7 +1087,6 @@ pub const CreateOptions = struct {
     /// executable this field is ignored.
     want_compiler_rt: ?bool = null,
     want_lto: ?bool = null,
-    formatted_panics: ?bool = null,
     function_sections: bool = false,
     data_sections: bool = false,
     no_builtin: bool = false,
@@ -1357,9 +1355,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
             }
         }
 
-        // TODO: https://github.com/ziglang/zig/issues/17969
-        const formatted_panics = options.formatted_panics orelse (options.root_mod.optimize_mode == .Debug);
-
         const error_limit = options.error_limit orelse (std.math.maxInt(u16) - 1);
 
         // We put everything into the cache hash that *cannot be modified
@@ -1520,7 +1515,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
             .verbose_link = options.verbose_link,
             .disable_c_depfile = options.disable_c_depfile,
             .reference_trace = options.reference_trace,
-            .formatted_panics = formatted_panics,
             .time_report = options.time_report,
             .stack_report = options.stack_report,
             .test_filters = options.test_filters,
@@ -1638,7 +1632,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
                 hash.addListOfBytes(options.test_filters);
                 hash.addOptionalBytes(options.test_name_prefix);
                 hash.add(options.skip_linker_dependencies);
-                hash.add(formatted_panics);
                 hash.add(options.emit_h != null);
                 hash.add(error_limit);
 
@@ -2564,7 +2557,6 @@ fn addNonIncrementalStuffToCacheManifest(
         man.hash.addListOfBytes(comp.test_filters);
         man.hash.addOptionalBytes(comp.test_name_prefix);
         man.hash.add(comp.skip_linker_dependencies);
-        man.hash.add(comp.formatted_panics);
         //man.hash.add(mod.emit_h != null);
         man.hash.add(mod.error_limit);
     } else {
src/crash_report.zig
@@ -17,7 +17,7 @@ const Decl = Zcu.Decl;
 /// 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) compilerPanic else std.builtin.default_panic;
+pub const panic = if (build_options.enable_debug_extensions) compilerPanic else std.debug.defaultPanic;
 
 /// Install signal handlers to identify crashes and report diagnostics.
 pub fn initialize() void {
@@ -152,12 +152,16 @@ fn writeFilePath(file: *Zcu.File, writer: anytype) !void {
     try writer.writeAll(file.sub_file_path);
 }
 
-pub fn compilerPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, maybe_ret_addr: ?usize) noreturn {
+pub fn compilerPanic(
+    cause: std.builtin.PanicCause,
+    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, msg);
+    PanicSwitch.dispatch(error_return_trace, stack_ctx, cause);
 }
 
 /// Attaches a global SIGSEGV handler
@@ -354,17 +358,17 @@ const PanicSwitch = struct {
     pub fn dispatch(
         trace: ?*const std.builtin.StackTrace,
         stack_ctx: StackContext,
-        msg: []const u8,
+        panic_cause: std.builtin.PanicCause,
     ) 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, 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 }),
+            .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 }),
             .silent_abort => goTo(abort, .{}),
         };
     }
@@ -373,7 +377,7 @@ const PanicSwitch = struct {
         state: *volatile PanicState,
         trace: ?*const std.builtin.StackTrace,
         stack: StackContext,
-        msg: []const u8,
+        panic_cause: std.builtin.PanicCause,
     ) noreturn {
         // use a temporary so there's only one volatile store
         const new_state = PanicState{
@@ -398,6 +402,8 @@ 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;
@@ -413,9 +419,9 @@ const PanicSwitch = struct {
         state: *volatile PanicState,
         trace: ?*const std.builtin.StackTrace,
         stack: StackContext,
-        msg: []const u8,
+        panic_cause: std.builtin.PanicCause,
     ) noreturn {
-        recover(state, trace, stack, msg);
+        recover(state, trace, stack, panic_cause);
 
         state.recover_stage = .release_mutex;
         const stderr = io.getStdErr().writer();
@@ -438,9 +444,9 @@ const PanicSwitch = struct {
         state: *volatile PanicState,
         trace: ?*const std.builtin.StackTrace,
         stack: StackContext,
-        msg: []const u8,
+        panic_cause: std.builtin.PanicCause,
     ) noreturn {
-        recover(state, trace, stack, msg);
+        recover(state, trace, stack, panic_cause);
         goTo(releaseMutex, .{state});
     }
 
@@ -456,9 +462,9 @@ const PanicSwitch = struct {
         state: *volatile PanicState,
         trace: ?*const std.builtin.StackTrace,
         stack: StackContext,
-        msg: []const u8,
+        panic_cause: std.builtin.PanicCause,
     ) noreturn {
-        recover(state, trace, stack, msg);
+        recover(state, trace, stack, panic_cause);
         goTo(releaseRefCount, .{state});
     }
 
@@ -484,9 +490,9 @@ const PanicSwitch = struct {
         state: *volatile PanicState,
         trace: ?*const std.builtin.StackTrace,
         stack: StackContext,
-        msg: []const u8,
+        panic_cause: std.builtin.PanicCause,
     ) noreturn {
-        recover(state, trace, stack, msg);
+        recover(state, trace, stack, panic_cause);
 
         state.recover_stage = .silent_abort;
         const stderr = io.getStdErr().writer();
@@ -510,7 +516,7 @@ const PanicSwitch = struct {
         state: *volatile PanicState,
         trace: ?*const std.builtin.StackTrace,
         stack: StackContext,
-        msg: []const u8,
+        panic_cause: std.builtin.PanicCause,
     ) void {
         switch (state.recover_verbosity) {
             .message_and_stack => {
@@ -519,7 +525,7 @@ const PanicSwitch = struct {
 
                 const stderr = io.getStdErr().writer();
                 stderr.writeAll("\nPanicked during a panic: ") catch {};
-                stderr.writeAll(msg) catch {};
+                stderr.writeAll(panic_cause) catch {};
                 stderr.writeAll("\nInner panic stack:\n") catch {};
                 if (trace) |t| {
                     debug.dumpStackTrace(t.*);
@@ -533,7 +539,7 @@ const PanicSwitch = struct {
 
                 const stderr = io.getStdErr().writer();
                 stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {};
-                stderr.writeAll(msg) catch {};
+                stderr.writeAll(panic_cause) catch {};
                 stderr.writeAll("\n") catch {};
 
                 // If we succeed, restore all the way to dumping the stack.
src/InternPool.zig
@@ -7353,6 +7353,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
         .func_type => unreachable, // use getFuncType() instead
         .@"extern" => unreachable, // use getExtern() instead
         .func => unreachable, // use getFuncInstance() or getFuncDecl() instead
+        .un => unreachable, // use getUnion instead
 
         .variable => |variable| {
             const has_init = variable.init != .none;
@@ -7968,15 +7969,6 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
             if (sentinel != .none) extra.appendAssumeCapacity(.{@intFromEnum(sentinel)});
         },
 
-        .un => |un| {
-            assert(un.ty != .none);
-            assert(un.val != .none);
-            items.appendAssumeCapacity(.{
-                .tag = .union_value,
-                .data = try addExtra(extra, un),
-            });
-        },
-
         .memoized_call => |memoized_call| {
             for (memoized_call.arg_values) |arg| assert(arg != .none);
             try extra.ensureUnusedCapacity(@typeInfo(MemoizedCall).@"struct".fields.len +
@@ -7996,6 +7988,30 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
     return gop.put();
 }
 
+pub fn getUnion(
+    ip: *InternPool,
+    gpa: Allocator,
+    tid: Zcu.PerThread.Id,
+    un: Key.Union,
+) Allocator.Error!Index {
+    var gop = try ip.getOrPutKey(gpa, tid, .{ .un = un });
+    defer gop.deinit();
+    if (gop == .existing) return gop.existing;
+    const local = ip.getLocal(tid);
+    const items = local.getMutableItems(gpa);
+    const extra = local.getMutableExtra(gpa);
+    try items.ensureUnusedCapacity(1);
+
+    assert(un.ty != .none);
+    assert(un.val != .none);
+    items.appendAssumeCapacity(.{
+        .tag = .union_value,
+        .data = try addExtra(extra, un),
+    });
+
+    return gop.put();
+}
+
 pub const UnionTypeInit = struct {
     flags: packed struct {
         runtime_tag: LoadedUnionType.RuntimeTag,
src/main.zig
@@ -826,7 +826,6 @@ fn buildOutputType(
     var version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 };
     var have_version = false;
     var compatibility_version: ?std.SemanticVersion = null;
-    var formatted_panics: ?bool = null;
     var function_sections = false;
     var data_sections = false;
     var no_builtin = false;
@@ -1537,9 +1536,11 @@ fn buildOutputType(
                     } else if (mem.eql(u8, arg, "-gdwarf64")) {
                         create_module.opts.debug_format = .{ .dwarf = .@"64" };
                     } else if (mem.eql(u8, arg, "-fformatted-panics")) {
-                        formatted_panics = true;
+                        // Remove this after 0.15.0 is tagged.
+                        warn("-fformatted-panics is deprecated and does nothing", .{});
                     } else if (mem.eql(u8, arg, "-fno-formatted-panics")) {
-                        formatted_panics = false;
+                        // Remove this after 0.15.0 is tagged.
+                        warn("-fno-formatted-panics is deprecated and does nothing", .{});
                     } else if (mem.eql(u8, arg, "-fsingle-threaded")) {
                         mod_opts.single_threaded = true;
                     } else if (mem.eql(u8, arg, "-fno-single-threaded")) {
@@ -3405,7 +3406,6 @@ fn buildOutputType(
         .force_undefined_symbols = force_undefined_symbols,
         .stack_size = stack_size,
         .image_base = image_base,
-        .formatted_panics = formatted_panics,
         .function_sections = function_sections,
         .data_sections = data_sections,
         .no_builtin = no_builtin,
src/mutable_value.zig
@@ -88,11 +88,11 @@ pub const MutableValue = union(enum) {
                 .ptr = (try s.ptr.intern(pt, arena)).toIntern(),
                 .len = (try s.len.intern(pt, arena)).toIntern(),
             } }),
-            .un => |u| try pt.intern(.{ .un = .{
+            .un => |u| try pt.internUnion(.{
                 .ty = u.ty,
                 .tag = u.tag,
                 .val = (try u.payload.intern(pt, arena)).toIntern(),
-            } }),
+            }),
         });
     }
 
src/Sema.zig
@@ -4854,11 +4854,11 @@ fn validateUnionInit(
         }
         block.instructions.shrinkRetainingCapacity(block_index);
 
-        const union_val = try pt.intern(.{ .un = .{
+        const union_val = try pt.internUnion(.{
             .ty = union_ty.toIntern(),
             .tag = tag_val.toIntern(),
             .val = val.toIntern(),
-        } });
+        });
         const union_init = Air.internedToRef(union_val);
         try sema.storePtr2(block, init_src, union_ptr, init_src, union_init, init_src, .store);
         return;
@@ -5830,8 +5830,6 @@ 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) {
@@ -5844,7 +5842,7 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
         sema.branch_hint = .cold;
     }
 
-    try sema.panicWithMsg(block, src, coerced_msg, .@"@panic");
+    try callPanic(sema, block, src, .explicit_call, coerced_msg, .@"@panic");
 }
 
 fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
@@ -7325,6 +7323,31 @@ 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;
+    if (!pt.zcu.backendSupportsFeature(.panic_fn)) {
+        _ = try block.addNoOp(.trap);
+        return;
+    }
+    const panic_cause_ty = try pt.getBuiltinType("PanicCause");
+    const panic_cause = try block.addUnionInit(panic_cause_ty, @intFromEnum(tag), payload);
+    const panic_fn = try pt.getBuiltin("panic");
+    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) };
+    try sema.callBuiltin(block, call_src, panic_fn, .auto, &args, call_operation);
+}
+
 const CallOperation = enum {
     call,
     @"@call",
@@ -9327,7 +9350,7 @@ fn analyzeErrUnionPayload(
     if (safety_check and block.wantSafety() and
         !err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu))
     {
-        try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err);
+        try sema.addSafetyCheckUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err);
     }
 
     return block.addTyOp(.unwrap_errunion_payload, payload_ty, operand);
@@ -9411,7 +9434,7 @@ fn analyzeErrUnionPayloadPtr(
     if (safety_check and block.wantSafety() and
         !err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu))
     {
-        try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);
+        try sema.addSafetyCheckUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);
     }
 
     if (initializing) {
@@ -14190,7 +14213,6 @@ fn maybeErrorUnwrap(
 ) !bool {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    if (!zcu.backendSupportsFeature(.panic_unwrap_error)) return false;
 
     const tags = sema.code.instructions.items(.tag);
     for (body) |inst| {
@@ -14223,25 +14245,13 @@ fn maybeErrorUnwrap(
             .as_node => try sema.zirAsNode(block, inst),
             .field_val => try sema.zirFieldVal(block, inst),
             .@"unreachable" => {
-                if (!zcu.comp.formatted_panics) {
-                    try sema.safetyPanic(block, operand_src, .unwrap_error);
-                    return true;
-                }
-
-                const panic_fn = try pt.getBuiltin("panicUnwrapError");
-                const err_return_trace = try sema.getErrorReturnTrace(block);
-                const args: [2]Air.Inst.Ref = .{ err_return_trace, operand };
-                try sema.callBuiltin(block, operand_src, panic_fn, .auto, &args, .@"safety check");
+                try callPanic(sema, block, operand_src, .unwrap_error, operand, .@"safety check");
                 return true;
             },
             .panic => {
                 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 pt.getBuiltin("panic");
-                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");
+                try callPanic(sema, block, operand_src, .explicit_call, msg_inst, .@"@panic");
                 return true;
             },
             else => unreachable,
@@ -17388,9 +17398,6 @@ 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.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) {
@@ -18319,29 +18326,14 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         .undefined,
         .null,
         .enum_literal,
-        => |type_info_tag| return Air.internedToRef((try pt.intern(.{ .un = .{
+        => |type_info_tag| return Air.internedToRef((try pt.internUnion(.{
             .ty = type_info_ty.toIntern(),
             .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(type_info_tag))).toIntern(),
             .val = .void_value,
-        } }))),
+        }))),
         .@"fn" => {
-            const fn_info_nav = try sema.namespaceLookup(
-                block,
-                src,
-                type_info_ty.getNamespaceIndex(zcu),
-                try ip.getOrPutString(gpa, pt.tid, "Fn", .no_embedded_nulls),
-            ) orelse @panic("std.builtin.Type is corrupt");
-            try sema.ensureNavResolved(src, fn_info_nav);
-            const fn_info_ty = Type.fromInterned(ip.getNav(fn_info_nav).status.resolved.val);
-
-            const param_info_nav = try sema.namespaceLookup(
-                block,
-                src,
-                fn_info_ty.getNamespaceIndex(zcu),
-                try ip.getOrPutString(gpa, pt.tid, "Param", .no_embedded_nulls),
-            ) orelse @panic("std.builtin.Type is corrupt");
-            try sema.ensureNavResolved(src, param_info_nav);
-            const param_info_ty = Type.fromInterned(ip.getNav(param_info_nav).status.resolved.val);
+            const fn_info_ty = try getInnerType(sema, block, src, type_info_ty, "Fn");
+            const param_info_ty = try getInnerType(sema, block, src, fn_info_ty, "Param");
 
             const func_ty_info = zcu.typeToFunc(ty).?;
             const param_vals = try sema.arena.alloc(InternPool.Index, func_ty_info.param_types.len);
@@ -18425,25 +18417,17 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // args: []const Fn.Param,
                 args_val,
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"fn"))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = fn_info_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .int => {
-            const int_info_nav = try sema.namespaceLookup(
-                block,
-                src,
-                type_info_ty.getNamespaceIndex(zcu),
-                try ip.getOrPutString(gpa, pt.tid, "Int", .no_embedded_nulls),
-            ) orelse @panic("std.builtin.Type is corrupt");
-            try sema.ensureNavResolved(src, int_info_nav);
-            const int_info_ty = Type.fromInterned(ip.getNav(int_info_nav).status.resolved.val);
-
+            const int_info_ty = try getInnerType(sema, block, src, type_info_ty, "Int");
             const signedness_ty = try pt.getBuiltinType("Signedness");
             const info = ty.intInfo(zcu);
             const field_values = .{
@@ -18452,37 +18436,30 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // bits: u16,
                 (try pt.intValue(Type.u16, info.bits)).toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.int))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = int_info_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .float => {
-            const float_info_nav = try sema.namespaceLookup(
-                block,
-                src,
-                type_info_ty.getNamespaceIndex(zcu),
-                try ip.getOrPutString(gpa, pt.tid, "Float", .no_embedded_nulls),
-            ) orelse @panic("std.builtin.Type is corrupt");
-            try sema.ensureNavResolved(src, float_info_nav);
-            const float_info_ty = Type.fromInterned(ip.getNav(float_info_nav).status.resolved.val);
+            const float_info_ty = try getInnerType(sema, block, src, type_info_ty, "Float");
 
             const field_vals = .{
                 // bits: u16,
                 (try pt.intValue(Type.u16, ty.bitSize(zcu))).toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.float))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = float_info_ty.toIntern(),
                     .storage = .{ .elems = &field_vals },
                 } }),
-            } })));
+            })));
         },
         .pointer => {
             const info = ty.ptrInfo(zcu);
@@ -18492,26 +18469,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 try Type.fromInterned(info.child).lazyAbiAlignment(pt);
 
             const addrspace_ty = try pt.getBuiltinType("AddressSpace");
-            const pointer_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    (try pt.getBuiltinType("Type")).getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Pointer", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
-            const ptr_size_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    pointer_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Size", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const pointer_ty = try getInnerType(sema, block, src, type_info_ty, "Pointer");
+            const ptr_size_ty = try getInnerType(sema, block, src, pointer_ty, "Size");
 
             const field_values = .{
                 // size: Size,
@@ -18534,26 +18493,17 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     else => Value.fromInterned(info.sentinel),
                 })).toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.pointer))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = pointer_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .array => {
-            const array_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Array", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const array_field_ty = try getInnerType(sema, block, src, type_info_ty, "Array");
 
             const info = ty.arrayInfo(zcu);
             const field_values = .{
@@ -18564,26 +18514,17 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // sentinel: ?*const anyopaque,
                 (try sema.optRefValue(info.sentinel)).toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.array))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = array_field_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .vector => {
-            const vector_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Vector", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const vector_field_ty = try getInnerType(sema, block, src, type_info_ty, "Vector");
 
             const info = ty.arrayInfo(zcu);
             const field_values = .{
@@ -18592,52 +18533,34 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // child: type,
                 info.elem_type.toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.vector))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = vector_field_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .optional => {
-            const optional_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Optional", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const optional_field_ty = try getInnerType(sema, block, src, type_info_ty, "Optional");
 
             const field_values = .{
                 // child: type,
                 ty.optionalChild(zcu).toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.optional))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = optional_field_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .error_set => {
             // Get the Error type
-            const error_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Error", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const error_field_ty = try getInnerType(sema, block, src, type_info_ty, "Error");
 
             // Build our list of Error values
             // Optional value is only null if anyerror
@@ -18726,23 +18649,14 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             } });
 
             // Construct Type{ .error_set = errors_val }
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.error_set))).toIntern(),
                 .val = errors_val,
-            } })));
+            })));
         },
         .error_union => {
-            const error_union_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "ErrorUnion", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const error_union_field_ty = try getInnerType(sema, block, src, type_info_ty, "ErrorUnion");
 
             const field_values = .{
                 // error_set: type,
@@ -18750,28 +18664,19 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // payload: type,
                 ty.errorUnionPayload(zcu).toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.error_union))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = error_union_field_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .@"enum" => {
             const is_exhaustive = Value.makeBool(ip.loadEnumType(ty.toIntern()).tag_mode != .nonexhaustive);
 
-            const enum_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "EnumField", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const enum_field_ty = try getInnerType(sema, block, src, type_info_ty, "EnumField");
 
             const enum_field_vals = try sema.arena.alloc(InternPool.Index, ip.loadEnumType(ty.toIntern()).names.len);
             for (enum_field_vals, 0..) |*field_val, tag_index| {
@@ -18858,16 +18763,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, ip.loadEnumType(ty.toIntern()).namespace.toOptional());
 
-            const type_enum_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Enum", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const type_enum_ty = try getInnerType(sema, block, src, type_info_ty, "Enum");
 
             const field_values = .{
                 // tag_type: type,
@@ -18879,37 +18775,18 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // is_exhaustive: bool,
                 is_exhaustive.toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"enum"))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = type_enum_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .@"union" => {
-            const type_union_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Union", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
-
-            const union_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "UnionField", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const type_union_ty = try getInnerType(sema, block, src, type_info_ty, "Union");
+            const union_field_ty = try getInnerType(sema, block, src, type_info_ty, "UnionField");
 
             try ty.resolveLayout(pt); // Getting alignment requires type layout
             const union_obj = zcu.typeToUnion(ty).?;
@@ -19004,16 +18881,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 .val = if (ty.unionTagType(zcu)) |tag_ty| tag_ty.toIntern() else .none,
             } });
 
-            const container_layout_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    (try pt.getBuiltinType("Type")).getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "ContainerLayout", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const container_layout_ty = try getBuiltinInnerType(sema, block, src, "Type", "ContainerLayout");
 
             const field_values = .{
                 // layout: ContainerLayout,
@@ -19026,37 +18894,18 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // decls: []const Declaration,
                 decls_val,
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"union"))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = type_union_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .@"struct" => {
-            const type_struct_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Struct", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
-
-            const struct_field_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "StructField", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const type_struct_ty = try getInnerType(sema, block, src, type_info_ty, "Struct");
+            const struct_field_ty = try getInnerType(sema, block, src, type_info_ty, "StructField");
 
             try ty.resolveLayout(pt); // Getting alignment requires type layout
 
@@ -19233,16 +19082,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 } else .none,
             } });
 
-            const container_layout_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    (try pt.getBuiltinType("Type")).getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "ContainerLayout", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const container_layout_ty = try getInnerType(sema, block, src, type_info_ty, "ContainerLayout");
 
             const layout = ty.containerLayout(zcu);
 
@@ -19258,26 +19098,17 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // is_tuple: bool,
                 Value.makeBool(ty.isTuple(zcu)).toIntern(),
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"struct"))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = type_struct_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .@"opaque" => {
-            const type_opaque_ty = t: {
-                const nav = try sema.namespaceLookup(
-                    block,
-                    src,
-                    type_info_ty.getNamespaceIndex(zcu),
-                    try ip.getOrPutString(gpa, pt.tid, "Opaque", .no_embedded_nulls),
-                ) orelse @panic("std.builtin.Type is corrupt");
-                try sema.ensureNavResolved(src, nav);
-                break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-            };
+            const type_opaque_ty = try getInnerType(sema, block, src, type_info_ty, "Opaque");
 
             try ty.resolveFields(pt);
             const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, ty.getNamespace(zcu));
@@ -19286,14 +19117,14 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // decls: []const Declaration,
                 decls_val,
             };
-            return Air.internedToRef((try pt.intern(.{ .un = .{
+            return Air.internedToRef((try pt.internUnion(.{
                 .ty = type_info_ty.toIntern(),
                 .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"opaque"))).toIntern(),
                 .val = try pt.intern(.{ .aggregate = .{
                     .ty = type_opaque_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
-            } })));
+            })));
         },
         .frame => return sema.failWithUseOfAsync(block, src),
         .@"anyframe" => return sema.failWithUseOfAsync(block, src),
@@ -19309,19 +19140,9 @@ fn typeInfoDecls(
 ) CompileError!InternPool.Index {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
     const gpa = sema.gpa;
 
-    const declaration_ty = t: {
-        const nav = try sema.namespaceLookup(
-            block,
-            src,
-            type_info_ty.getNamespaceIndex(zcu),
-            try ip.getOrPutString(gpa, pt.tid, "Declaration", .no_embedded_nulls),
-        ) orelse @panic("std.builtin.Type is corrupt");
-        try sema.ensureNavResolved(src, nav);
-        break :t Type.fromInterned(ip.getNav(nav).status.resolved.val);
-    };
+    const declaration_ty = try getInnerType(sema, block, src, type_info_ty, "Declaration");
 
     var decl_vals = std.ArrayList(InternPool.Index).init(gpa);
     defer decl_vals.deinit();
@@ -20809,11 +20630,11 @@ fn unionInit(
     if (try sema.resolveValue(init)) |init_val| {
         const tag_ty = union_ty.unionTagTypeHypothetical(zcu);
         const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index);
-        return Air.internedToRef((try pt.intern(.{ .un = .{
+        return Air.internedToRef((try pt.internUnion(.{
             .ty = union_ty.toIntern(),
             .tag = tag_val.toIntern(),
             .val = init_val.toIntern(),
-        } })));
+        })));
     }
 
     try sema.requireRuntimeBlock(block, init_src, null);
@@ -20949,11 +20770,11 @@ fn zirStructInit(
         const init_inst = try sema.coerce(block, field_ty, uncoerced_init_inst, field_src);
 
         if (try sema.resolveValue(init_inst)) |val| {
-            const struct_val = Value.fromInterned(try pt.intern(.{ .un = .{
+            const struct_val = Value.fromInterned(try pt.internUnion(.{
                 .ty = resolved_ty.toIntern(),
                 .tag = tag_val.toIntern(),
                 .val = val.toIntern(),
-            } }));
+            }));
             const final_val_inst = try sema.coerce(block, result_ty, Air.internedToRef(struct_val.toIntern()), src);
             const final_val = (try sema.resolveValue(final_val_inst)).?;
             return sema.addConstantMaybeRef(final_val.toIntern(), is_ref);
@@ -21869,11 +21690,20 @@ 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;
     const ip = &zcu.intern_pool;
-
     try operand_ty.resolveLayout(pt);
     const enum_ty = switch (operand_ty.zigTypeTag(zcu)) {
         .enum_literal => {
@@ -27840,7 +27670,7 @@ fn explainWhyTypeIsNotPacked(
     }
 }
 
-fn prepareSimplePanic(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
+fn preparePanic(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
     const pt = sema.pt;
     const zcu = pt.zcu;
 
@@ -27871,33 +27701,12 @@ fn prepareSimplePanic(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
             .val = .none,
         } });
     }
-}
-
-/// 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;
-    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;
+    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();
+    }
 }
 
 fn addSafetyCheck(
@@ -27905,7 +27714,7 @@ fn addSafetyCheck(
     parent_block: *Block,
     src: LazySrcLoc,
     ok: Air.Inst.Ref,
-    panic_id: Zcu.PanicId,
+    panic_cause_tag: PanicCauseTag,
 ) !void {
     const gpa = sema.gpa;
     assert(!parent_block.is_comptime);
@@ -27923,7 +27732,7 @@ fn addSafetyCheck(
 
     defer fail_block.instructions.deinit(gpa);
 
-    try sema.safetyPanic(&fail_block, src, panic_id);
+    try sema.safetyPanic(&fail_block, src, panic_cause_tag);
     try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
 }
 
@@ -27992,30 +27801,7 @@ 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 panicUnwrapError(
+fn addSafetyCheckUnwrapError(
     sema: *Sema,
     parent_block: *Block,
     src: LazySrcLoc,
@@ -28023,12 +27809,8 @@ fn panicUnwrapError(
     unwrap_err_tag: Air.Inst.Tag,
     is_non_err_tag: Air.Inst.Tag,
 ) !void {
-    const pt = sema.pt;
     assert(!parent_block.is_comptime);
     const ok = try parent_block.addUnOp(is_non_err_tag, operand);
-    if (!pt.zcu.comp.formatted_panics) {
-        return sema.addSafetyCheck(parent_block, src, ok, .unwrap_error);
-    }
     const gpa = sema.gpa;
 
     var fail_block: Block = .{
@@ -28044,21 +27826,13 @@ fn panicUnwrapError(
 
     defer fail_block.instructions.deinit(gpa);
 
-    {
-        if (!pt.zcu.backendSupportsFeature(.panic_unwrap_error)) {
-            _ = try fail_block.addNoOp(.trap);
-        } else {
-            const panic_fn = try sema.pt.getBuiltin("panicUnwrapError");
-            const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand);
-            const err_return_trace = try sema.getErrorReturnTrace(&fail_block);
-            const args: [2]Air.Inst.Ref = .{ err_return_trace, err };
-            try sema.callBuiltin(&fail_block, src, panic_fn, .auto, &args, .@"safety check");
-        }
-    }
+    const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand);
+    try callPanic(sema, &fail_block, src, .unwrap_error, err, .@"safety check");
+
     try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
 }
 
-fn panicIndexOutOfBounds(
+fn addSafetyCheckIndexOob(
     sema: *Sema,
     parent_block: *Block,
     src: LazySrcLoc,
@@ -28067,14 +27841,71 @@ fn panicIndexOutOfBounds(
     cmp_op: Air.Inst.Tag,
 ) !void {
     assert(!parent_block.is_comptime);
+    const gpa = sema.gpa;
     const ok = try parent_block.addBinOp(cmp_op, index, len);
-    if (!sema.pt.zcu.comp.formatted_panics) {
-        return sema.addSafetyCheck(parent_block, src, ok, .index_out_of_bounds);
+
+    var fail_block: Block = .{
+        .parent = parent_block,
+        .sema = sema,
+        .namespace = parent_block.namespace,
+        .instructions = .{},
+        .inlining = parent_block.inlining,
+        .is_comptime = false,
+        .src_base_inst = parent_block.src_base_inst,
+        .type_name_ctx = parent_block.type_name_ctx,
+    };
+
+    defer fail_block.instructions.deinit(gpa);
+
+    const oob_ty = try getBuiltinInnerType(sema, &fail_block, src, "PanicCause", "IndexOutOfBounds");
+    comptime {
+        const fields = @typeInfo(std.builtin.PanicCause.IndexOutOfBounds).@"struct".fields;
+        assert(std.mem.eql(u8, fields[0].name, "index"));
+        assert(std.mem.eql(u8, fields[1].name, "len"));
+        assert(fields.len == 2);
     }
-    try sema.safetyCheckFormatted(parent_block, src, ok, "panicOutOfBounds", &.{ index, len });
+    const panic_cause_payload = try fail_block.addAggregateInit(oob_ty, &.{ index, len });
+    try callPanic(sema, &fail_block, src, .index_out_of_bounds, panic_cause_payload, .@"safety check");
+    try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
 }
 
-fn panicInactiveUnionField(
+fn addSafetyCheckStartGreaterThanEnd(
+    sema: *Sema,
+    parent_block: *Block,
+    src: LazySrcLoc,
+    start: Air.Inst.Ref,
+    end: Air.Inst.Ref,
+) !void {
+    assert(!parent_block.is_comptime);
+    const gpa = sema.gpa;
+    const ok = try parent_block.addBinOp(.cmp_lte, start, end);
+
+    var fail_block: Block = .{
+        .parent = parent_block,
+        .sema = sema,
+        .namespace = parent_block.namespace,
+        .instructions = .{},
+        .inlining = parent_block.inlining,
+        .is_comptime = false,
+        .src_base_inst = parent_block.src_base_inst,
+        .type_name_ctx = parent_block.type_name_ctx,
+    };
+
+    defer fail_block.instructions.deinit(gpa);
+
+    const oob_ty = try getBuiltinInnerType(sema, &fail_block, src, "PanicCause", "StartIndexGreaterThanEnd");
+    comptime {
+        const fields = @typeInfo(std.builtin.PanicCause.StartIndexGreaterThanEnd).@"struct".fields;
+        assert(std.mem.eql(u8, fields[0].name, "start"));
+        assert(std.mem.eql(u8, fields[1].name, "end"));
+        assert(fields.len == 2);
+    }
+    const panic_cause_payload = try fail_block.addAggregateInit(oob_ty, &.{ start, end });
+    try callPanic(sema, &fail_block, src, .start_index_greater_than_end, panic_cause_payload, .@"safety check");
+    try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
+}
+
+fn addSafetyCheckInactiveUnionField(
     sema: *Sema,
     parent_block: *Block,
     src: LazySrcLoc,
@@ -28082,14 +27913,39 @@ fn panicInactiveUnionField(
     wanted_tag: Air.Inst.Ref,
 ) !void {
     assert(!parent_block.is_comptime);
+    const gpa = sema.gpa;
     const ok = try parent_block.addBinOp(.cmp_eq, active_tag, wanted_tag);
-    if (!sema.pt.zcu.comp.formatted_panics) {
-        return sema.addSafetyCheck(parent_block, src, ok, .inactive_union_field);
-    }
-    try sema.safetyCheckFormatted(parent_block, src, ok, "panicInactiveUnionField", &.{ active_tag, wanted_tag });
+
+    var fail_block: Block = .{
+        .parent = parent_block,
+        .sema = sema,
+        .namespace = parent_block.namespace,
+        .instructions = .{},
+        .inlining = parent_block.inlining,
+        .is_comptime = false,
+        .src_base_inst = parent_block.src_base_inst,
+        .type_name_ctx = parent_block.type_name_ctx,
+    };
+
+    defer fail_block.instructions.deinit(gpa);
+
+    const payload_ty = try getBuiltinInnerType(sema, &fail_block, src, "PanicCause", "InactiveUnionField");
+    comptime {
+        const fields = @typeInfo(std.builtin.PanicCause.InactiveUnionField).@"struct".fields;
+        assert(std.mem.eql(u8, fields[0].name, "active"));
+        assert(std.mem.eql(u8, fields[1].name, "accessed"));
+        assert(fields.len == 2);
+    }
+    // TODO: before merging the branch, check how many safety checks end up being emitted
+    // for union field accesses and avoid extraneous ones.
+    const active_str = try analyzeTagName(sema, &fail_block, src, src, active_tag);
+    const accessed_str = try analyzeTagName(sema, &fail_block, src, src, wanted_tag);
+    const panic_cause_payload = try fail_block.addAggregateInit(payload_ty, &.{ active_str, accessed_str });
+    try callPanic(sema, &fail_block, src, .inactive_union_field, panic_cause_payload, .@"safety check");
+    try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
 }
 
-fn panicSentinelMismatch(
+fn addSafetyCheckSentinelMismatch(
     sema: *Sema,
     parent_block: *Block,
     src: LazySrcLoc,
@@ -28099,6 +27955,7 @@ fn panicSentinelMismatch(
     sentinel_index: Air.Inst.Ref,
 ) !void {
     assert(!parent_block.is_comptime);
+    const gpa = sema.gpa;
     const pt = sema.pt;
     const zcu = pt.zcu;
     const expected_sentinel_val = maybe_sentinel orelse return;
@@ -28114,8 +27971,7 @@ fn panicSentinelMismatch(
     };
 
     const ok = if (sentinel_ty.zigTypeTag(zcu) == .vector) ok: {
-        const eql =
-            try parent_block.addCmpVector(expected_sentinel, actual_sentinel, .eq);
+        const eql = try parent_block.addCmpVector(expected_sentinel, actual_sentinel, .eq);
         break :ok try parent_block.addInst(.{
             .tag = .reduce,
             .data = .{ .reduce = .{
@@ -28128,25 +27984,6 @@ fn panicSentinelMismatch(
         break :ok try parent_block.addBinOp(.cmp_eq, expected_sentinel, actual_sentinel);
     };
 
-    if (!pt.zcu.comp.formatted_panics) {
-        return sema.addSafetyCheck(parent_block, src, ok, .sentinel_mismatch);
-    }
-    try sema.safetyCheckFormatted(parent_block, src, ok, "panicSentinelMismatch", &.{ expected_sentinel, actual_sentinel });
-}
-
-fn safetyCheckFormatted(
-    sema: *Sema,
-    parent_block: *Block,
-    src: LazySrcLoc,
-    ok: Air.Inst.Ref,
-    func: []const u8,
-    args: []const Air.Inst.Ref,
-) CompileError!void {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    assert(zcu.comp.formatted_panics);
-    const gpa = sema.gpa;
-
     var fail_block: Block = .{
         .parent = parent_block,
         .sema = sema,
@@ -28160,20 +27997,29 @@ fn safetyCheckFormatted(
 
     defer fail_block.instructions.deinit(gpa);
 
-    if (!zcu.backendSupportsFeature(.safety_check_formatted)) {
-        _ = try fail_block.addNoOp(.trap);
+    // A different PanicCause tag must be used depending on what payload type it can be fit into.
+    // If it cannot fit into any, the "other" tag can be used, which does not try to carry the
+    // sentinel value data.
+
+    if (sentinel_ty.isUnsignedInt(zcu) and sentinel_ty.intInfo(zcu).bits <= Type.usize.intInfo(zcu).bits) {
+        const mm_ty = try getBuiltinInnerType(sema, &fail_block, src, "PanicCause", "SentinelMismatchUsize");
+        comptime {
+            const fields = @typeInfo(std.builtin.PanicCause.SentinelMismatchUsize).@"struct".fields;
+            assert(std.mem.eql(u8, fields[0].name, "expected"));
+            assert(std.mem.eql(u8, fields[1].name, "found"));
+            assert(fields.len == 2);
+        }
+        const panic_cause_payload = &fail_block.addAggregateInit(mm_ty, &.{ expected_sentinel, actual_sentinel });
+        try callPanic(sema, &fail_block, src, .sentinel_mismatch_usize, panic_cause_payload, .@"safety check");
     } else {
-        const panic_fn = try pt.getBuiltin(func);
-        try sema.callBuiltin(&fail_block, src, panic_fn, .auto, args, .@"safety check");
+        try callPanic(sema, &fail_block, src, .sentinel_mismatch_other, .void_value, .@"safety check");
     }
     try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
 }
 
 /// This does not set `sema.branch_hint`.
-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 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 emitBackwardBranch(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
@@ -29229,7 +29075,7 @@ fn unionFieldPtr(
         // TODO would it be better if get_union_tag supported pointers to unions?
         const union_val = try block.addTyOp(.load, union_ty, union_ptr);
         const active_tag = try block.addTyOp(.get_union_tag, Type.fromInterned(union_obj.enum_tag_ty), union_val);
-        try sema.panicInactiveUnionField(block, src, active_tag, wanted_tag);
+        try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, wanted_tag);
     }
     if (field_ty.zigTypeTag(zcu) == .noreturn) {
         _ = try block.addNoOp(.unreach);
@@ -29304,7 +29150,7 @@ fn unionFieldVal(
         const wanted_tag_val = try pt.enumValueFieldIndex(Type.fromInterned(union_obj.enum_tag_ty), enum_field_index);
         const wanted_tag = Air.internedToRef(wanted_tag_val.toIntern());
         const active_tag = try block.addTyOp(.get_union_tag, Type.fromInterned(union_obj.enum_tag_ty), union_byval);
-        try sema.panicInactiveUnionField(block, src, active_tag, wanted_tag);
+        try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, wanted_tag);
     }
     if (field_ty.zigTypeTag(zcu) == .noreturn) {
         _ = try block.addNoOp(.unreach);
@@ -29668,11 +29514,11 @@ fn elemValArray(
 
     const runtime_src = if (maybe_undef_array_val != null) elem_index_src else array_src;
     if (oob_safety and block.wantSafety()) {
-        // Runtime check is only needed if unable to comptime check
+        // Runtime check is only needed if unable to comptime check.
         if (maybe_index_val == null) {
             const len_inst = try pt.intRef(Type.usize, array_len);
             const cmp_op: Air.Inst.Tag = if (array_sent != null) .cmp_lte else .cmp_lt;
-            try sema.panicIndexOutOfBounds(block, src, elem_index, len_inst, cmp_op);
+            try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
         }
     }
 
@@ -29740,7 +29586,7 @@ fn elemPtrArray(
     if (oob_safety and block.wantSafety() and offset == null) {
         const len_inst = try pt.intRef(Type.usize, array_len);
         const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt;
-        try sema.panicIndexOutOfBounds(block, src, elem_index, len_inst, cmp_op);
+        try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
     }
 
     return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty);
@@ -29799,7 +29645,7 @@ fn elemValSlice(
         else
             try block.addTyOp(.slice_len, Type.usize, slice);
         const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
-        try sema.panicIndexOutOfBounds(block, src, elem_index, len_inst, cmp_op);
+        try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
     }
     return block.addBinOp(.slice_elem_val, slice, elem_index);
 }
@@ -29859,7 +29705,7 @@ fn elemPtrSlice(
             break :len try block.addTyOp(.slice_len, Type.usize, slice);
         };
         const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
-        try sema.panicIndexOutOfBounds(block, src, elem_index, len_inst, cmp_op);
+        try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
     }
     return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty);
 }
@@ -33666,12 +33512,7 @@ fn analyzeSlice(
         // requirement: start <= end
         assert(!block.is_comptime);
         try sema.requireRuntimeBlock(block, src, runtime_src.?);
-        const ok = try block.addBinOp(.cmp_lte, start, end);
-        if (!pt.zcu.comp.formatted_panics) {
-            try sema.addSafetyCheck(block, src, ok, .start_index_greater_than_end);
-        } else {
-            try sema.safetyCheckFormatted(block, src, ok, "panicStartGreaterThanEnd", &.{ start, end });
-        }
+        try sema.addSafetyCheckStartGreaterThanEnd(block, src, start, end);
     }
     const new_len = if (by_length)
         try sema.coerce(block, Type.usize, uncasted_end_opt, end_src)
@@ -33726,11 +33567,11 @@ fn analyzeSlice(
                     else
                         end;
 
-                    try sema.panicIndexOutOfBounds(block, src, actual_end, actual_len, .cmp_lte);
+                    try sema.addSafetyCheckIndexOob(block, src, actual_end, actual_len, .cmp_lte);
                 }
 
                 // requirement: result[new_len] == slice_sentinel
-                try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
+                try sema.addSafetyCheckSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
             }
             return result;
         };
@@ -33789,11 +33630,11 @@ fn analyzeSlice(
                 try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src, true)
             else
                 end;
-            try sema.panicIndexOutOfBounds(block, src, actual_end, len_inst, .cmp_lte);
+            try sema.addSafetyCheckIndexOob(block, src, actual_end, len_inst, .cmp_lte);
         }
 
         // requirement: start <= end
-        try sema.panicIndexOutOfBounds(block, src, start, end, .cmp_lte);
+        try sema.addSafetyCheckIndexOob(block, src, start, end, .cmp_lte);
     }
     const result = try block.addInst(.{
         .tag = .slice,
@@ -33807,7 +33648,7 @@ fn analyzeSlice(
     });
     if (block.wantSafety()) {
         // requirement: result[new_len] == slice_sentinel
-        try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
+        try sema.addSafetyCheckSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
     }
     return result;
 }
@@ -37688,11 +37529,11 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                     const only_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
                     const val_val = (try sema.typeHasOnePossibleValue(only_field_ty)) orelse
                         return null;
-                    const only = try pt.intern(.{ .un = .{
+                    const only = try pt.internUnion(.{
                         .ty = ty.toIntern(),
                         .tag = tag_val.toIntern(),
                         .val = val_val.toIntern(),
-                    } });
+                    });
                     return Value.fromInterned(only);
                 },
 
@@ -38849,7 +38690,7 @@ fn analyzeUnreachable(sema: *Sema, block: *Block, src: LazySrcLoc, safety_check:
             sema.branch_hint = .cold;
         }
 
-        try sema.safetyPanic(block, src, .unreach);
+        try sema.safetyPanic(block, src, .reached_unreachable);
     } else {
         _ = try block.addNoOp(.unreach);
     }
@@ -39123,3 +38964,36 @@ const loadComptimePtr = @import("Sema/comptime_ptr_access.zig").loadComptimePtr;
 const ComptimeLoadResult = @import("Sema/comptime_ptr_access.zig").ComptimeLoadResult;
 const storeComptimePtr = @import("Sema/comptime_ptr_access.zig").storeComptimePtr;
 const ComptimeStoreResult = @import("Sema/comptime_ptr_access.zig").ComptimeStoreResult;
+
+/// Convenience function that looks 2 levels deep into `std.builtin`.
+fn getBuiltinInnerType(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    outer_name: []const u8,
+    inner_name: []const u8,
+) !Type {
+    const outer_ty = try sema.pt.getBuiltinType(outer_name);
+    return getInnerType(sema, block, src, outer_ty, inner_name);
+}
+
+fn getInnerType(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    outer_ty: Type,
+    inner_name: []const u8,
+) !Type {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const ip = &zcu.intern_pool;
+    const gpa = sema.gpa;
+    const nav = try sema.namespaceLookup(
+        block,
+        src,
+        outer_ty.getNamespaceIndex(zcu),
+        try ip.getOrPutString(gpa, pt.tid, inner_name, .no_embedded_nulls),
+    ) orelse return sema.fail(block, src, "std.builtin missing {s}", .{inner_name});
+    try sema.ensureNavResolved(src, nav);
+    return Type.fromInterned(ip.getNav(nav).status.resolved.val);
+}
src/target.zig
@@ -586,14 +586,6 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt
             => true,
             else => false,
         },
-        .panic_unwrap_error => switch (backend) {
-            .stage2_c, .stage2_llvm => true,
-            else => false,
-        },
-        .safety_check_formatted => switch (backend) {
-            .stage2_c, .stage2_llvm => true,
-            else => false,
-        },
         .error_return_trace => switch (backend) {
             .stage2_llvm => true,
             else => false,
src/Type.zig
@@ -2677,11 +2677,11 @@ pub fn onePossibleValue(starting_type: Type, pt: Zcu.PerThread) !?Value {
                 const only_field_ty = union_obj.field_types.get(ip)[0];
                 const val_val = (try Type.fromInterned(only_field_ty).onePossibleValue(pt)) orelse
                     return null;
-                const only = try pt.intern(.{ .un = .{
+                const only = try pt.internUnion(.{
                     .ty = ty.toIntern(),
                     .tag = tag_val.toIntern(),
                     .val = val_val.toIntern(),
-                } });
+                });
                 return Value.fromInterned(only);
             },
             .opaque_type => return null,
src/Value.zig
@@ -713,11 +713,11 @@ pub fn readFromMemory(
                 const union_size = ty.abiSize(zcu);
                 const array_ty = try zcu.arrayType(.{ .len = union_size, .child = .u8_type });
                 const val = (try readFromMemory(array_ty, zcu, buffer, arena)).toIntern();
-                return Value.fromInterned(try pt.intern(.{ .un = .{
+                return Value.fromInterned(try pt.internUnion(.{
                     .ty = ty.toIntern(),
                     .tag = .none,
                     .val = val,
-                } }));
+                }));
             },
             .@"packed" => {
                 const byte_count = (@as(usize, @intCast(ty.bitSize(zcu))) + 7) / 8;
@@ -860,11 +860,11 @@ pub fn readFromPackedMemory(
             .@"packed" => {
                 const backing_ty = try ty.unionBackingType(pt);
                 const val = (try readFromPackedMemory(backing_ty, pt, buffer, bit_offset, arena)).toIntern();
-                return Value.fromInterned(try pt.intern(.{ .un = .{
+                return Value.fromInterned(try pt.internUnion(.{
                     .ty = ty.toIntern(),
                     .tag = .none,
                     .val = val,
-                } }));
+                }));
             },
         },
         .pointer => {
@@ -4481,11 +4481,11 @@ pub fn resolveLazy(
             return if (resolved_tag == un.tag and resolved_val == un.val)
                 val
             else
-                Value.fromInterned(try pt.intern(.{ .un = .{
+                Value.fromInterned(try pt.internUnion(.{
                     .ty = un.ty,
                     .tag = resolved_tag,
                     .val = resolved_val,
-                } }));
+                }));
         },
         else => return val,
     }
src/Zcu.zig
@@ -210,45 +210,15 @@ 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,
 
 generation: u32 = 0,
 
 pub const PerThread = @import("Zcu/PerThread.zig");
 
-pub const PanicId = enum {
-    unreach,
-    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,
-    integer_part_out_of_bounds,
-    corrupt_switch,
-    shift_rhs_too_big,
-    invalid_enum_value,
-    sentinel_mismatch,
-    unwrap_error,
-    index_out_of_bounds,
-    start_index_greater_than_end,
-    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 {
@@ -2926,14 +2896,6 @@ pub const Feature = enum {
     /// When this feature is enabled, Sema will emit calls to `std.builtin.panic`
     /// for things like safety checks and unreachables. Otherwise traps will be emitted.
     panic_fn,
-    /// When this feature is enabled, Sema will emit calls to `std.builtin.panicUnwrapError`.
-    /// This error message requires more advanced formatting, hence it being seperate from `panic_fn`.
-    /// Otherwise traps will be emitted.
-    panic_unwrap_error,
-    /// When this feature is enabled, Sema will emit calls to the more complex panic functions
-    /// that use formatting to add detail to error messages. Similar to `panic_unwrap_error`.
-    /// Otherwise traps will be emitted.
-    safety_check_formatted,
     /// When this feature is enabled, Sema will insert tracer functions for gathering a stack
     /// trace for error returns.
     error_return_trace,
test/cases/safety/unreachable.zig
@@ -1,7 +1,8 @@
 const std = @import("std");
 
-pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
     _ = stack_trace;
+    _ = ret_addr;
     if (std.mem.eql(u8, message, "reached unreachable code")) {
         std.process.exit(0);
     }