Commit f2c8940aa6

Andrew Kelley <andrew@ziglang.org>
2024-09-26 01:20:19
reintroduce the std.builtin safety panic helpers
motivated by performance
1 parent 76f0b6e
Changed files (3)
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;
 }
 
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;
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)