diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 4042bc274304..2ed53329daa2 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -26,39 +26,6 @@ pub const subsystem: ?std.Target.SubSystem = blk: { } }; -/// This data structure is used by the Zig language code generation and -/// therefore must be kept in sync with the compiler implementation. -pub const StackTrace = struct { - index: usize, - instruction_addresses: []usize, - - pub fn format( - self: StackTrace, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, self); - - // TODO: re-evaluate whether to use format() methods at all. - // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly - // where it tries to call detectTTYConfig here. - if (builtin.os.tag == .freestanding) return; - - _ = options; - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const debug_info = std.debug.getSelfDebugInfo() catch |err| { - return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); - }; - const tty_config = std.io.tty.detectConfig(std.io.getStdErr()); - try writer.writeAll("\n"); - std.debug.writeStackTrace(self, writer, arena.allocator(), debug_info, tty_config) catch |err| { - try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)}); - }; - } -}; - /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const GlobalLinkage = enum { @@ -717,7 +684,27 @@ 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; +const CheckNonScalarSentinelFn = fn (anytype, anytype) void; + +/// This function type is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +const PanicSentinelMismatchFn = fn (anytype, anytype) noreturn; + +/// This function type is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +const PanicUnwrapErrorFn = fn (?*StackTrace, anyerror) noreturn; + +/// This function type is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +const PanicOutOfBoundsFn = fn (usize, usize) noreturn; + +/// This function type is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +const PanicInactiveUnionFieldFn = fn (anytype, anytype) noreturn; + +/// This function type is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +const PanicFn = fn ([]const u8, ?*default.StackTrace, ?usize) noreturn; /// This function is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. @@ -726,132 +713,75 @@ pub const panic: PanicFn = if (@hasDecl(root, "panic")) else if (@hasDecl(root, "os") and @hasDecl(root.os, "panic")) root.os.panic else - default_panic; + default.panic; /// This function 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 { - @setCold(true); - - // 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) or - builtin.zig_backend == .stage2_riscv64 or - builtin.zig_backend == .stage2_sparc64 or - builtin.zig_backend == .stage2_spirv64) - { - while (true) { - @breakpoint(); - } - } - switch (builtin.os.tag) { - .freestanding => { - while (true) { - @breakpoint(); - } - }, - .wasi => { - std.debug.print("{s}", .{msg}); - std.os.abort(); - }, - .uefi => { - const uefi = std.os.uefi; - - const ExitData = struct { - pub fn create_exit_data(exit_msg: []const u8, 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 - var utf16: []u16 = try uefi.raw_pool_allocator.alloc(u16, 256); - errdefer uefi.raw_pool_allocator.free(utf16); - - if (exit_msg.len > 255) { - return error.MessageTooLong; - } - - var fmt: [256]u8 = undefined; - var slice = try std.fmt.bufPrint(&fmt, "\r\nerr: {s}\r\n", .{exit_msg}); - - var len = try std.unicode.utf8ToUtf16Le(utf16, slice); - - utf16[len] = 0; - exit_size.* = 256; - - return @as([*:0]u16, @ptrCast(utf16.ptr)); - } - }; - - var exit_size: usize = 0; - var exit_data = ExitData.create_exit_data(msg, &exit_size) catch null; - - if (exit_data) |data| { - if (uefi.system_table.std_err) |out| { - _ = out.setAttribute(uefi.protocol.SimpleTextOutput.red); - _ = out.outputString(data); - _ = out.setAttribute(uefi.protocol.SimpleTextOutput.white); - } - } - - if (uefi.system_table.boot_services) |bs| { - _ = bs.exit(uefi.handle, .Aborted, exit_size, exit_data); - } +pub const panicInactiveUnionField: PanicInactiveUnionFieldFn = if (@hasDecl(root, "panicInactiveUnionField")) + root.panicInactiveUnionField +else if (@hasDecl(root, "os") and @hasDecl(root.os, "panicInactiveUnionField")) + root.os.panicInactiveUnionField +else + default.panicInactiveUnionField; - // Didn't have boot_services, just fallback to whatever. - std.os.abort(); - }, - .cuda, .amdhsa => std.os.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); - }, - } -} +/// This function is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const panicOutOfBounds: PanicOutOfBoundsFn = if (@hasDecl(root, "panicOutOfBounds")) + root.panicOutOfBounds +else if (@hasDecl(root, "os") and @hasDecl(root.os, "panicOutOfBounds")) + root.os.panicOutOfBounds +else + default.panicOutOfBounds; -pub fn checkNonScalarSentinel(expected: anytype, actual: @TypeOf(expected)) void { - if (!std.meta.eql(expected, actual)) { - panicSentinelMismatch(expected, actual); - } -} +/// This function is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const panicSentinelMismatch: PanicSentinelMismatchFn = if (@hasDecl(root, "panicSentinelMismatch")) + root.panicSentinelMismatch +else if (@hasDecl(root, "os") and @hasDecl(root.os, "panicSentinelMismatch")) + root.os.panicSentinelMismatch +else + default.panicSentinelMismatch; -pub fn panicSentinelMismatch(expected: anytype, actual: @TypeOf(expected)) noreturn { - @setCold(true); - std.debug.panicExtra(null, @returnAddress(), "sentinel mismatch: expected {any}, found {any}", .{ expected, actual }); -} +/// This function is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const checkNonScalarSentinel: CheckNonScalarSentinelFn = if (@hasDecl(root, "checkNonScalarSentinel")) + root.checkNonScalarSentinel +else if (@hasDecl(root, "os") and @hasDecl(root.os, "checkNonScalarSentinel")) + root.os.checkNonScalarSentinel +else + default.checkNonScalarSentinel; -pub fn panicUnwrapError(st: ?*StackTrace, err: anyerror) noreturn { - @setCold(true); - std.debug.panicExtra(st, @returnAddress(), "attempt to unwrap error: {s}", .{@errorName(err)}); -} +/// This function is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const panicStartGreaterThanEnd: PanicOutOfBoundsFn = if (@hasDecl(root, "panicStartGreaterThanEnd")) + root.panicStartGreaterThanEnd +else if (@hasDecl(root, "os") and @hasDecl(root.os, "panicStartGreaterThanEnd")) + root.os.panicStartGreaterThanEnd +else + default.panicStartGreaterThanEnd; -pub fn panicOutOfBounds(index: usize, len: usize) noreturn { - @setCold(true); - std.debug.panicExtra(null, @returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len }); -} +/// This function is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const panicUnwrapError: PanicUnwrapErrorFn = if (@hasDecl(root, "panicUnwrapError")) + root.panicUnwrapError +else if (@hasDecl(root, "os") and @hasDecl(root.os, "panicUnwrapError")) + root.os.panicUnwrapError +else + default.panicUnwrapError; -pub fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn { - @setCold(true); - std.debug.panicExtra(null, @returnAddress(), "start index {d} is larger than end index {d}", .{ start, end }); -} +/// This function is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const StackTrace = if (@hasDecl(root, "StackTrace")) + root.StackTrace +else if (@hasDecl(root, "os") and @hasDecl(root.os, "StackTrace")) + root.os.StackTrace +else + default.StackTrace; -pub fn panicInactiveUnionField(active: anytype, wanted: @TypeOf(active)) noreturn { - @setCold(true); - std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ @tagName(wanted), @tagName(active) }); -} +pub const default_panic = default.panic; +pub const returnError = StackTrace.returnError; +pub const addErrRetTraceAddr = StackTrace.addErrRetTraceAddr; pub const panic_messages = struct { pub const unreach = "reached unreachable code"; @@ -881,18 +811,175 @@ pub const panic_messages = struct { pub const noreturn_returned = "'noreturn' function returned"; }; -pub noinline fn returnError(st: *StackTrace) void { - @setCold(true); - @setRuntimeSafety(false); - addErrRetTraceAddr(st, @returnAddress()); -} +const default = struct { + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + const StackTrace = struct { + index: usize, + instruction_addresses: []usize, + + pub fn format( + self: default.StackTrace, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, self); + + // TODO: re-evaluate whether to use format() methods at all. + // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly + // where it tries to call detectTTYConfig here. + if (builtin.os.tag == .freestanding) return; + + _ = options; + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const debug_info = std.debug.getSelfDebugInfo() catch |err| { + return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); + }; + const tty_config = std.io.tty.detectConfig(std.io.getStdErr()); + try writer.writeAll("\n"); + std.debug.writeStackTrace(self, writer, arena.allocator(), debug_info, tty_config) catch |err| { + try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)}); + }; + } + noinline fn returnError(st: *default.StackTrace) void { + @setCold(true); + @setRuntimeSafety(false); + st.addErrRetTraceAddr(@returnAddress()); + } + inline fn addErrRetTraceAddr(st: *default.StackTrace, addr: usize) void { + if (st.index < st.instruction_addresses.len) { + st.instruction_addresses[st.index] = addr; + } + st.index += 1; + } + }; + + /// This function is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + fn panic(msg: []const u8, error_return_trace: ?*default.StackTrace, ret_addr: ?usize) noreturn { + @setCold(true); + + // 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) or + builtin.zig_backend == .stage2_riscv64 or + builtin.zig_backend == .stage2_sparc64 or + builtin.zig_backend == .stage2_spirv64) + { + while (true) { + @breakpoint(); + } + } + switch (builtin.os.tag) { + .freestanding => { + while (true) { + @breakpoint(); + } + }, + .wasi => { + std.debug.print("{s}", .{msg}); + std.os.abort(); + }, + .uefi => { + const uefi = std.os.uefi; + + const ExitData = struct { + pub fn create_exit_data(exit_msg: []const u8, exit_size: *usize) ![*:0]u16 { + // Need boot services for pool allocation + if (uefi.system_table.boot_services == null) { + return error.BootServicesUnavailable; + } -pub inline fn addErrRetTraceAddr(st: *StackTrace, addr: usize) void { - if (st.index < st.instruction_addresses.len) - st.instruction_addresses[st.index] = addr; + // ExitData buffer must be allocated using boot_services.allocatePool + var utf16: []u16 = try uefi.raw_pool_allocator.alloc(u16, 256); + errdefer uefi.raw_pool_allocator.free(utf16); - st.index += 1; -} + if (exit_msg.len > 255) { + return error.MessageTooLong; + } + var fmt: [256]u8 = undefined; + var slice = try std.fmt.bufPrint(&fmt, "\r\nerr: {s}\r\n", .{exit_msg}); + + var len = try std.unicode.utf8ToUtf16Le(utf16, slice); + + utf16[len] = 0; + + exit_size.* = 256; + + return @as([*:0]u16, @ptrCast(utf16.ptr)); + } + }; + + var exit_size: usize = 0; + var exit_data = ExitData.create_exit_data(msg, &exit_size) catch null; + + if (exit_data) |data| { + if (uefi.system_table.std_err) |out| { + _ = out.setAttribute(uefi.protocol.SimpleTextOutput.red); + _ = out.outputString(data); + _ = 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.os.abort(); + }, + .cuda, .amdhsa => std.os.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); + }, + } + } + + fn checkNonScalarSentinel(expected: anytype, actual: @TypeOf(expected)) void { + if (!std.meta.eql(expected, actual)) { + default.panicSentinelMismatch(expected, actual); + } + } + + fn panicSentinelMismatch(expected: anytype, actual: @TypeOf(expected)) noreturn { + @setCold(true); + std.debug.panicExtra(null, @returnAddress(), "sentinel mismatch: expected {any}, found {any}", .{ expected, actual }); + } + + fn panicUnwrapError(st: ?*default.StackTrace, err: anyerror) noreturn { + @setCold(true); + std.debug.panicExtra(st, @returnAddress(), "attempt to unwrap error: {s}", .{@errorName(err)}); + } + + fn panicOutOfBounds(index: usize, len: usize) noreturn { + @setCold(true); + std.debug.panicExtra(null, @returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len }); + } + + fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn { + @setCold(true); + std.debug.panicExtra(null, @returnAddress(), "start index {d} is larger than end index {d}", .{ start, end }); + } + + fn panicInactiveUnionField(active: anytype, wanted: @TypeOf(active)) noreturn { + @setCold(true); + std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ @tagName(wanted), @tagName(active) }); + } +}; const std = @import("std.zig"); const root = @import("root");