reintroduce the std.builtin safety panic helpers

motivated by performance
This commit is contained in:
Andrew Kelley 2024-09-25 16:20:19 -07:00
parent 76f0b6e7d8
commit f2c8940aa6
3 changed files with 126 additions and 117 deletions

View File

@ -803,6 +803,7 @@ pub const PanicCause = union(enum) {
memcpy_alias,
noreturn_returned,
explicit_call: []const u8,
sentinel_mismatch_isize: SentinelMismatchIsize,
pub const IndexOutOfBounds = struct {
index: usize,
@ -819,22 +820,82 @@ pub const PanicCause = union(enum) {
found: usize,
};
pub const SentinelMismatchIsize = struct {
expected: isize,
found: isize,
};
pub const InactiveUnionField = struct {
active: []const u8,
accessed: []const u8,
};
};
pub noinline fn returnError(st: *StackTrace) void {
pub fn panicSentinelMismatch(expected: anytype, found: @TypeOf(expected)) noreturn {
@branchHint(.cold);
@setRuntimeSafety(false);
addErrRetTraceAddr(st, @returnAddress());
switch (@typeInfo(@TypeOf(expected))) {
.int => |int| switch (int.signedness) {
.unsigned => if (int.bits <= @bitSizeOf(usize)) panic(.{ .sentinel_mismatch_usize = .{
.expected = expected,
.found = found,
} }, null, @returnAddress()),
.signed => if (int.bits <= @bitSizeOf(isize)) panic(.{ .sentinel_mismatch_isize = .{
.expected = expected,
.found = found,
} }, null, @returnAddress()),
},
.@"enum" => |info| switch (@typeInfo(info.tag_type)) {
.int => |int| switch (int.signedness) {
.unsigned => if (int.bits <= @bitSizeOf(usize)) panic(.{ .sentinel_mismatch_usize = .{
.expected = @intFromEnum(expected),
.found = @intFromEnum(found),
} }, null, @returnAddress()),
.signed => if (int.bits <= @bitSizeOf(isize)) panic(.{ .sentinel_mismatch_isize = .{
.expected = @intFromEnum(expected),
.found = @intFromEnum(found),
} }, null, @returnAddress()),
},
else => comptime unreachable,
},
else => {},
}
panic(.sentinel_mismatch_other, null, @returnAddress());
}
pub inline fn addErrRetTraceAddr(st: *StackTrace, addr: usize) void {
if (st.index < st.instruction_addresses.len)
st.instruction_addresses[st.index] = addr;
pub fn panicUnwrapError(ert: ?*StackTrace, err: anyerror) noreturn {
@branchHint(.cold);
panic(.{ .unwrap_error = err }, ert, @returnAddress());
}
pub fn panicOutOfBounds(index: usize, len: usize) noreturn {
@branchHint(.cold);
panic(.{ .index_out_of_bounds = .{
.index = index,
.len = len,
} }, null, @returnAddress());
}
pub fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn {
@branchHint(.cold);
panic(.{ .start_index_greater_than_end = .{
.start = start,
.end = end,
} }, null, @returnAddress());
}
pub fn panicInactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn {
@branchHint(.cold);
panic(.{ .inactive_union_field = .{
.active = @tagName(active),
.accessed = @tagName(accessed),
} }, null, @returnAddress());
}
pub noinline fn returnError(st: *StackTrace) void {
@branchHint(.unlikely);
@setRuntimeSafety(false);
if (st.index < st.instruction_addresses.len)
st.instruction_addresses[st.index] = @returnAddress();
st.index += 1;
}

View File

@ -453,7 +453,7 @@ threadlocal var panic_stage: usize = 0;
// This function avoids a dependency on formatted printing.
pub fn defaultPanic(
cause: std.builtin.PanicCause,
trace: ?*const std.builtin.StackTrace,
error_return_trace: ?*const std.builtin.StackTrace,
first_trace_addr: ?usize,
) noreturn {
@branchHint(.cold);
@ -568,7 +568,7 @@ pub fn defaultPanic(
defer unlockStdErr();
io.getStdErr().writeAll(msg) catch posix.abort();
if (trace) |t| dumpStackTrace(t.*);
if (error_return_trace) |t| dumpStackTrace(t.*);
dumpCurrentStackTrace(first_trace_addr orelse @returnAddress());
}
@ -621,6 +621,12 @@ pub fn fmtPanicCause(buffer: []u8, cause: std.builtin.PanicCause) usize {
i += fmtBuf(buffer[i..], ", found ");
i += fmtInt10(buffer[i..], mm.found);
},
.sentinel_mismatch_isize => |mm| {
i += fmtBuf(buffer[i..], "sentinel mismatch: expected ");
i += fmtInt10s(buffer[i..], mm.expected);
i += fmtBuf(buffer[i..], ", found ");
i += fmtInt10s(buffer[i..], mm.found);
},
.sentinel_mismatch_other => i += fmtBuf(buffer[i..], "sentinel mismatch"),
.unwrap_error => |err| {
i += fmtBuf(buffer[i..], "attempt to unwrap error: ");
@ -653,6 +659,15 @@ fn fmtBuf(out_buf: []u8, s: []const u8) usize {
return s.len;
}
fn fmtInt10s(out_buf: []u8, integer_value: isize) usize {
if (integer_value < 0) {
out_buf[0] = '-';
return 1 + fmtInt10(out_buf[1..], @abs(integer_value));
} else {
return fmtInt10(out_buf, @abs(integer_value));
}
}
fn fmtInt10(out_buf: []u8, integer_value: usize) usize {
var tmp_buf: [50]u8 = undefined;
var i: usize = tmp_buf.len;

View File

@ -14249,7 +14249,7 @@ fn maybeErrorUnwrap(
.as_node => try sema.zirAsNode(block, inst),
.field_val => try sema.zirFieldVal(block, inst),
.@"unreachable" => {
try callPanic(sema, block, operand_src, .unwrap_error, operand, .@"safety check");
try safetyPanicUnwrapError(sema, block, operand_src, operand);
return true;
},
.panic => {
@ -27827,11 +27827,24 @@ fn addSafetyCheckUnwrapError(
defer fail_block.instructions.deinit(gpa);
const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand);
try callPanic(sema, &fail_block, src, .unwrap_error, err, .@"safety check");
try safetyPanicUnwrapError(sema, &fail_block, src, err);
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
}
fn safetyPanicUnwrapError(sema: *Sema, block: *Block, src: LazySrcLoc, err: Air.Inst.Ref) !void {
const pt = sema.pt;
const zcu = pt.zcu;
if (!zcu.backendSupportsFeature(.panic_fn)) {
_ = try block.addNoOp(.trap);
} else {
const panic_fn = try pt.getBuiltin("panicUnwrapError");
const err_return_trace = try sema.getErrorReturnTrace(block);
const args: [2]Air.Inst.Ref = .{ err_return_trace, err };
try sema.callBuiltin(block, src, panic_fn, .auto, &args, .@"safety check");
}
}
fn addSafetyCheckIndexOob(
sema: *Sema,
parent_block: *Block,
@ -27841,68 +27854,8 @@ fn addSafetyCheckIndexOob(
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);
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);
}
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 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);
return addSafetyCheckCall(sema, parent_block, src, ok, "panicOutOfBounds", &.{ index, len });
}
fn addSafetyCheckInactiveUnionField(
@ -27913,36 +27866,8 @@ fn addSafetyCheckInactiveUnionField(
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);
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);
return addSafetyCheckCall(sema, parent_block, src, ok, "panicInactiveUnionField", &.{ active_tag, wanted_tag });
}
fn addSafetyCheckSentinelMismatch(
@ -27955,7 +27880,6 @@ fn addSafetyCheckSentinelMismatch(
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;
@ -27984,6 +27908,24 @@ fn addSafetyCheckSentinelMismatch(
break :ok try parent_block.addBinOp(.cmp_eq, expected_sentinel, actual_sentinel);
};
return addSafetyCheckCall(sema, parent_block, src, ok, "panicSentinelMismatch", &.{
expected_sentinel, actual_sentinel,
});
}
fn addSafetyCheckCall(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
ok: Air.Inst.Ref,
func_name: []const u8,
args: []const Air.Inst.Ref,
) !void {
assert(!parent_block.is_comptime);
const gpa = sema.gpa;
const pt = sema.pt;
const zcu = pt.zcu;
var fail_block: Block = .{
.parent = parent_block,
.sema = sema,
@ -27997,23 +27939,13 @@ fn addSafetyCheckSentinelMismatch(
defer fail_block.instructions.deinit(gpa);
// 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 = try fail_block.addAggregateInit(mm_ty, &.{ expected_sentinel, actual_sentinel });
try callPanic(sema, &fail_block, src, .sentinel_mismatch_usize, panic_cause_payload, .@"safety check");
if (!zcu.backendSupportsFeature(.panic_fn)) {
_ = try fail_block.addNoOp(.trap);
} else {
try callPanic(sema, &fail_block, src, .sentinel_mismatch_other, .void_value, .@"safety check");
const panic_fn = try pt.getBuiltin(func_name);
try sema.callBuiltin(&fail_block, src, panic_fn, .auto, args, .@"safety check");
}
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
}
@ -33512,7 +33444,8 @@ fn analyzeSlice(
// requirement: start <= end
assert(!block.is_comptime);
try sema.requireRuntimeBlock(block, src, runtime_src.?);
try sema.addSafetyCheckStartGreaterThanEnd(block, src, start, end);
const ok = try block.addBinOp(.cmp_lte, start, end);
try sema.addSafetyCheckCall(block, src, ok, "panicStartGreaterThanEnd", &.{ start, end });
}
const new_len = if (by_length)
try sema.coerce(block, Type.usize, uncasted_end_opt, end_src)