From f2c8940aa6b1bd6ca20730e13b8148af002e1b91 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 25 Sep 2024 16:20:19 -0700 Subject: [PATCH] reintroduce the std.builtin safety panic helpers motivated by performance --- lib/std/builtin.zig | 73 +++++++++++++++++++-- lib/std/debug.zig | 19 +++++- src/Sema.zig | 151 ++++++++++++-------------------------------- 3 files changed, 126 insertions(+), 117 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 8530a64fed..4edcebad53 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -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; } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index d6312598d1..7bfc6b321e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -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; diff --git a/src/Sema.zig b/src/Sema.zig index fd4c8cfa4f..1b98ba3fe8 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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)