Skip to content

formalize the panic interface #21520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
04e694a
move std.time.sleep to std.Thread.sleep
andrewrk Sep 24, 2024
4f8d244
remove formatted panics
andrewrk Sep 25, 2024
9ccf8d3
fixes for this branch
andrewrk Sep 25, 2024
76f0b6e
delete the old panic stuff again
andrewrk Sep 25, 2024
f2c8940
reintroduce the std.builtin safety panic helpers
andrewrk Sep 25, 2024
b66cc5a
reimplement integer overflow safety panic function calls
andrewrk Sep 26, 2024
70746d5
better codegen for `@panic` with comptime-known operand
andrewrk Sep 26, 2024
61b2010
fix crash report not using mutexes correctly
andrewrk Sep 26, 2024
e888782
compile error instead of crash when root panic fn wrong
andrewrk Sep 26, 2024
231783f
update test cases to new panic API
andrewrk Sep 26, 2024
fcfbedc
work around riscv64 backend deficiencies
andrewrk Sep 26, 2024
c9c080a
embrace panic helpers
andrewrk Sep 26, 2024
1b491e6
fixes and make sema report errors when std.builtin wrong
andrewrk Sep 26, 2024
db8c074
fix still calling std.builtin.panic sometimes
andrewrk Sep 26, 2024
7f4c0e0
update safety test cases to new panic API
andrewrk Sep 27, 2024
37d1da5
I think it's better to put the imports at the top
andrewrk Sep 27, 2024
5b1a9fb
update crash report to the new panic interface
andrewrk Sep 27, 2024
2e14cbe
Sema: better utility function semantics
andrewrk Sep 27, 2024
737b581
disable plan9 test
andrewrk Sep 27, 2024
777e722
macho: increase pre-allocated vmsize for __TEXT_ZIG segment
kubkon Sep 27, 2024
2857ca1
revert safety test cases to rely on deprecated old API
andrewrk Sep 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions lib/compiler_rt/common.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,14 @@ pub const gnu_f16_abi = switch (builtin.cpu.arch) {

pub const want_sparc_abi = builtin.cpu.arch.isSPARC();

// Avoid dragging in the runtime safety mechanisms into this .o file,
// unless we're trying to test compiler-rt.
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = error_return_trace;
// Avoid dragging in the runtime safety mechanisms into this .o file, unless
// we're trying to test compiler-rt.
pub const Panic = if (builtin.is_test) std.debug.FormattedPanic else struct {};

/// To be deleted after zig1.wasm is updated.
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
if (builtin.is_test) {
@branchHint(.cold);
std.debug.panic("{s}", .{msg});
std.debug.defaultPanic(msg, error_return_trace, ret_addr orelse @returnAddress());
} else {
unreachable;
}
Expand Down
77 changes: 77 additions & 0 deletions lib/std/Thread.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,83 @@ pub const WaitGroup = @import("Thread/WaitGroup.zig");

pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;

/// Spurious wakeups are possible and no precision of timing is guaranteed.
pub fn sleep(nanoseconds: u64) void {
if (builtin.os.tag == .windows) {
const big_ms_from_ns = nanoseconds / std.time.ns_per_ms;
const ms = math.cast(windows.DWORD, big_ms_from_ns) orelse math.maxInt(windows.DWORD);
windows.kernel32.Sleep(ms);
return;
}

if (builtin.os.tag == .wasi) {
const w = std.os.wasi;
const userdata: w.userdata_t = 0x0123_45678;
const clock: w.subscription_clock_t = .{
.id = .MONOTONIC,
.timeout = nanoseconds,
.precision = 0,
.flags = 0,
};
const in: w.subscription_t = .{
.userdata = userdata,
.u = .{
.tag = .CLOCK,
.u = .{ .clock = clock },
},
};

var event: w.event_t = undefined;
var nevents: usize = undefined;
_ = w.poll_oneoff(&in, &event, 1, &nevents);
return;
}

if (builtin.os.tag == .uefi) {
const boot_services = std.os.uefi.system_table.boot_services.?;
const us_from_ns = nanoseconds / std.time.ns_per_us;
const us = math.cast(usize, us_from_ns) orelse math.maxInt(usize);
_ = boot_services.stall(us);
return;
}

const s = nanoseconds / std.time.ns_per_s;
const ns = nanoseconds % std.time.ns_per_s;

// Newer kernel ports don't have old `nanosleep()` and `clock_nanosleep()` has been around
// since Linux 2.6 and glibc 2.1 anyway.
if (builtin.os.tag == .linux) {
const linux = std.os.linux;

var req: linux.timespec = .{
.sec = std.math.cast(linux.time_t, s) orelse std.math.maxInt(linux.time_t),
.nsec = std.math.cast(linux.time_t, ns) orelse std.math.maxInt(linux.time_t),
};
var rem: linux.timespec = undefined;

while (true) {
switch (linux.E.init(linux.clock_nanosleep(.MONOTONIC, .{ .ABSTIME = false }, &req, &rem))) {
.SUCCESS => return,
.INTR => {
req = rem;
continue;
},
.FAULT,
.INVAL,
.OPNOTSUPP,
=> unreachable,
else => return,
}
}
}

posix.nanosleep(s, ns);
}

test sleep {
sleep(1);
}

const Thread = @This();
const Impl = if (native_os == .windows)
WindowsThreadImpl
Expand Down
225 changes: 42 additions & 183 deletions lib/std/builtin.zig
Original file line number Diff line number Diff line change
Expand Up @@ -761,195 +761,54 @@ pub const TestFn = struct {
func: *const fn () anyerror!void,
};

/// This function type is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
/// Deprecated, use the `Panic` namespace instead.
/// To be deleted after 0.14.0 is released.
pub const PanicFn = fn ([]const u8, ?*StackTrace, ?usize) noreturn;

/// This function is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const panic: PanicFn = if (@hasDecl(root, "panic"))
root.panic
else if (@hasDecl(root, "os") and @hasDecl(root.os, "panic"))
root.os.panic
/// Deprecated, use the `Panic` namespace instead.
/// To be deleted after 0.14.0 is released.
pub const panic: PanicFn = Panic.call;

/// This namespace is used by the Zig compiler to emit various kinds of safety
/// panics. These can be overridden by making a public `Panic` namespace in the
/// root source file.
pub const Panic: type = if (@hasDecl(root, "Panic"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a namespace instead of a comptime struct like std.Options to avoid generating unused decls? Shouldn't only accessing the fields at comptime already avoid unused default values being codegened?

root.Panic
else if (@hasDecl(root, "panic")) // Deprecated, use `Panic` instead.
DeprecatedPanic
else if (builtin.zig_backend == .stage2_riscv64)
std.debug.SimplePanic // https://github.com/ziglang/zig/issues/21519
else
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 {
@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 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 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 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";
};
std.debug.FormattedPanic;

/// To be deleted after 0.14.0 is released.
const DeprecatedPanic = struct {
pub const call = root.panic;
pub const sentinelMismatch = std.debug.FormattedPanic.sentinelMismatch;
pub const unwrapError = std.debug.FormattedPanic.unwrapError;
pub const outOfBounds = std.debug.FormattedPanic.outOfBounds;
pub const startGreaterThanEnd = std.debug.FormattedPanic.startGreaterThanEnd;
pub const inactiveUnionField = std.debug.FormattedPanic.inactiveUnionField;
pub const messages = std.debug.FormattedPanic.messages;
};

/// To be deleted after zig1.wasm is updated.
pub const panicSentinelMismatch = Panic.sentinelMismatch;
/// To be deleted after zig1.wasm is updated.
pub const panicUnwrapError = Panic.unwrapError;
/// To be deleted after zig1.wasm is updated.
pub const panicOutOfBounds = Panic.outOfBounds;
/// To be deleted after zig1.wasm is updated.
pub const panicStartGreaterThanEnd = Panic.startGreaterThanEnd;
/// To be deleted after zig1.wasm is updated.
pub const panicInactiveUnionField = Panic.inactiveUnionField;
/// To be deleted after zig1.wasm is updated.
pub const panic_messages = Panic.messages;

pub noinline fn returnError(st: *StackTrace) void {
@branchHint(.cold);
@branchHint(.unlikely);
@setRuntimeSafety(false);
addErrRetTraceAddr(st, @returnAddress());
}

pub inline fn addErrRetTraceAddr(st: *StackTrace, addr: usize) void {
if (st.index < st.instruction_addresses.len)
st.instruction_addresses[st.index] = addr;

st.instruction_addresses[st.index] = @returnAddress();
st.index += 1;
}

Expand Down
Loading