authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-01 07:41:24+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-01 12:10:57+01:00
logc1a5caa4545264b476951e844818f2abe103f41c
treefcacbb6cb46d76b9bb31a20a7c2149cb3802b7d7
parent6daa37ded905431f3a25508a42590031b0905ab9
signaturelock-open Commit is signed but in an unrecognized format.

compiler: combine `@intCast` safety checks

`castTruncatedData` was a poorly worded error (all shrinking casts "truncate bits", it's just that we assume those bits to be zext/sext of the other bits!), and `negativeToUnsigned` was a pointless distinction which forced the compiler to emit worse code (since two separate safety checks were required for casting e.g. 'i32' to 'u16') and wasn't even implemented correctly. This commit combines those safety panics into one function, `integerOutOfBounds`. The name maybe isn't perfect, but that's not hugely important; what matters is the new default message, which is clearer than the old ones: "integer does not fit in destination type".

20 files changed, 46 insertions(+), 74 deletions(-)

doc/langref/test_intCast_builtin.zig+1-1
......@@ -5,4 +5,4 @@ test "integer cast panic" {
55 _ = b;
66}
77
8// test_error=cast truncated bits
8// test_error=integer does not fit in destination type
lib/std/debug.zig+6-6
......@@ -78,13 +78,9 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
7878 @branchHint(.cold);
7979 call("invalid error code", @returnAddress());
8080 }
81 pub fn castTruncatedData() noreturn {
81 pub fn integerOutOfBounds() noreturn {
8282 @branchHint(.cold);
83 call("integer cast truncated bits", @returnAddress());
84 }
85 pub fn negativeToUnsigned() noreturn {
86 @branchHint(.cold);
87 call("attempt to cast negative value to unsigned integer", @returnAddress());
83 call("integer does not fit in destination type", @returnAddress());
8884 }
8985 pub fn integerOverflow() noreturn {
9086 @branchHint(.cold);
......@@ -128,6 +124,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
128124 }
129125 /// Delete after next zig1.wasm update
130126 pub const memcpyLenMismatch = copyLenMismatch;
127 /// Delete after next zig1.wasm update
128 pub const castTruncatedData = integerOutOfBounds;
129 /// Delete after next zig1.wasm update
130 pub const negativeToUnsigned = integerOutOfBounds;
131131 pub fn copyLenMismatch() noreturn {
132132 @branchHint(.cold);
133133 call("source and destination arguments have non-equal lengths", @returnAddress());
lib/std/debug/no_panic.zig+5-6
......@@ -65,12 +65,7 @@ pub fn invalidErrorCode() noreturn {
6565 @trap();
6666}
6767
68pub fn castTruncatedData() noreturn {
69 @branchHint(.cold);
70 @trap();
71}
72
73pub fn negativeToUnsigned() noreturn {
68pub fn integerOutOfBounds() noreturn {
7469 @branchHint(.cold);
7570 @trap();
7671}
......@@ -127,6 +122,10 @@ pub fn forLenMismatch() noreturn {
127122
128123/// Delete after next zig1.wasm update
129124pub const memcpyLenMismatch = copyLenMismatch;
125/// Delete after next zig1.wasm update
126pub const castTruncatedData = integerOutOfBounds;
127/// Delete after next zig1.wasm update
128pub const negativeToUnsigned = integerOutOfBounds;
130129
131130pub fn copyLenMismatch() noreturn {
132131 @branchHint(.cold);
lib/std/debug/simple_panic.zig+6-6
......@@ -72,12 +72,8 @@ pub fn invalidErrorCode() noreturn {
7272 call("invalid error code", null);
7373}
7474
75pub fn castTruncatedData() noreturn {
76 call("integer cast truncated bits", null);
77}
78
79pub fn negativeToUnsigned() noreturn {
80 call("attempt to cast negative value to unsigned integer", null);
75pub fn integerOutOfBounds() noreturn {
76 call("integer does not fit in destination type", null);
8177}
8278
8379pub fn integerOverflow() noreturn {
......@@ -122,6 +118,10 @@ pub fn forLenMismatch() noreturn {
122118
123119/// Delete after next zig1.wasm update
124120pub const memcpyLenMismatch = copyLenMismatch;
121/// Delete after next zig1.wasm update
122pub const castTruncatedData = integerOutOfBounds;
123/// Delete after next zig1.wasm update
124pub const negativeToUnsigned = integerOutOfBounds;
125125
126126pub fn copyLenMismatch() noreturn {
127127 call("source and destination have non-equal lengths", null);
src/Air/Legalize.zig+1-1
......@@ -1307,7 +1307,7 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
13071307 var main_block: Block = .init(&inst_buf);
13081308 var cur_block: *Block = &main_block;
13091309
1310 const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .cast_truncated_data;
1310 const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .integer_out_of_bounds;
13111311
13121312 if (have_min_check or have_max_check) {
13131313 const dest_int_ty = if (dest_is_enum) dest_ty.intTagType(zcu) else dest_ty;
src/Sema.zig+6-10
......@@ -10263,7 +10263,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
1026310263 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast");
1026410264 const operand = try sema.resolveInst(extra.rhs);
1026510265
10266 return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src, true, false);
10266 return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src);
1026710267}
1026810268
1026910269fn intCast(
......@@ -10274,8 +10274,6 @@ fn intCast(
1027410274 dest_ty_src: LazySrcLoc,
1027510275 operand: Air.Inst.Ref,
1027610276 operand_src: LazySrcLoc,
10277 runtime_safety: bool,
10278 safety_panics_are_enum: bool,
1027910277) CompileError!Air.Inst.Ref {
1028010278 const pt = sema.pt;
1028110279 const zcu = pt.zcu;
......@@ -10294,7 +10292,7 @@ fn intCast(
1029410292
1029510293 if ((try sema.typeHasOnePossibleValue(dest_ty))) |opv| {
1029610294 // requirement: intCast(u0, input) iff input == 0
10297 if (runtime_safety and block.wantSafety()) {
10295 if (block.wantSafety()) {
1029810296 try sema.requireRuntimeBlock(block, src, operand_src);
1029910297 const wanted_info = dest_scalar_ty.intInfo(zcu);
1030010298 const wanted_bits = wanted_info.bits;
......@@ -10311,7 +10309,7 @@ fn intCast(
1031110309 const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst);
1031210310 break :ok is_in_range;
1031310311 };
10314 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
10312 try sema.addSafetyCheck(block, src, ok, .integer_out_of_bounds);
1031510313 }
1031610314 }
1031710315
......@@ -10319,10 +10317,9 @@ fn intCast(
1031910317 }
1032010318
1032110319 try sema.requireRuntimeBlock(block, src, operand_src);
10322 if (runtime_safety and block.wantSafety()) {
10320 if (block.wantSafety()) {
1032310321 if (zcu.backendSupportsFeature(.panic_fn)) {
10324 _ = try sema.preparePanicId(src, .negative_to_unsigned);
10325 _ = try sema.preparePanicId(src, .cast_truncated_data);
10322 _ = try sema.preparePanicId(src, .integer_out_of_bounds);
1032610323 }
1032710324 return block.addTyOp(.intcast_safe, dest_ty, operand);
1032810325 }
......@@ -37984,8 +37981,7 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.BuiltinDecl) CompileError!Typ
3798437981 .@"panic.castToNull",
3798537982 .@"panic.incorrectAlignment",
3798637983 .@"panic.invalidErrorCode",
37987 .@"panic.castTruncatedData",
37988 .@"panic.negativeToUnsigned",
37984 .@"panic.integerOutOfBounds",
3798937985 .@"panic.integerOverflow",
3799037986 .@"panic.shlOverflow",
3799137987 .@"panic.shrOverflow",
src/Zcu.zig+4-8
......@@ -441,8 +441,7 @@ pub const BuiltinDecl = enum {
441441 @"panic.castToNull",
442442 @"panic.incorrectAlignment",
443443 @"panic.invalidErrorCode",
444 @"panic.castTruncatedData",
445 @"panic.negativeToUnsigned",
444 @"panic.integerOutOfBounds",
446445 @"panic.integerOverflow",
447446 @"panic.shlOverflow",
448447 @"panic.shrOverflow",
......@@ -518,8 +517,7 @@ pub const BuiltinDecl = enum {
518517 .@"panic.castToNull",
519518 .@"panic.incorrectAlignment",
520519 .@"panic.invalidErrorCode",
521 .@"panic.castTruncatedData",
522 .@"panic.negativeToUnsigned",
520 .@"panic.integerOutOfBounds",
523521 .@"panic.integerOverflow",
524522 .@"panic.shlOverflow",
525523 .@"panic.shrOverflow",
......@@ -585,8 +583,7 @@ pub const SimplePanicId = enum {
585583 cast_to_null,
586584 incorrect_alignment,
587585 invalid_error_code,
588 cast_truncated_data,
589 negative_to_unsigned,
586 integer_out_of_bounds,
590587 integer_overflow,
591588 shl_overflow,
592589 shr_overflow,
......@@ -609,8 +606,7 @@ pub const SimplePanicId = enum {
609606 .cast_to_null => .@"panic.castToNull",
610607 .incorrect_alignment => .@"panic.incorrectAlignment",
611608 .invalid_error_code => .@"panic.invalidErrorCode",
612 .cast_truncated_data => .@"panic.castTruncatedData",
613 .negative_to_unsigned => .@"panic.negativeToUnsigned",
609 .integer_out_of_bounds => .@"panic.integerOutOfBounds",
614610 .integer_overflow => .@"panic.integerOverflow",
615611 .shl_overflow => .@"panic.shlOverflow",
616612 .shr_overflow => .@"panic.shrOverflow",
src/codegen/llvm.zig+3-7
......@@ -9189,11 +9189,7 @@ pub const FuncGen = struct {
91899189 const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
91909190 assert(is_vector == (dest_ty.zigTypeTag(zcu) == .vector));
91919191
9192 const min_panic_id: Zcu.SimplePanicId, const max_panic_id: Zcu.SimplePanicId = id: {
9193 if (dest_is_enum) break :id .{ .invalid_enum_value, .invalid_enum_value };
9194 if (dest_info.signedness == .unsigned) break :id .{ .negative_to_unsigned, .cast_truncated_data };
9195 break :id .{ .cast_truncated_data, .cast_truncated_data };
9196 };
9192 const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .integer_out_of_bounds;
91979193
91989194 if (have_min_check) {
91999195 const min_const_scalar = try minIntConst(&o.builder, dest_scalar, operand_scalar_llvm_ty, zcu);
......@@ -9207,7 +9203,7 @@ pub const FuncGen = struct {
92079203 const ok_block = try fg.wip.block(1, "IntMinOk");
92089204 _ = try fg.wip.brCond(ok, ok_block, fail_block, .none);
92099205 fg.wip.cursor = .{ .block = fail_block };
9210 try fg.buildSimplePanic(min_panic_id);
9206 try fg.buildSimplePanic(panic_id);
92119207 fg.wip.cursor = .{ .block = ok_block };
92129208 }
92139209
......@@ -9223,7 +9219,7 @@ pub const FuncGen = struct {
92239219 const ok_block = try fg.wip.block(1, "IntMaxOk");
92249220 _ = try fg.wip.brCond(ok, ok_block, fail_block, .none);
92259221 fg.wip.cursor = .{ .block = fail_block };
9226 try fg.buildSimplePanic(max_panic_id);
9222 try fg.buildSimplePanic(panic_id);
92279223 fg.wip.cursor = .{ .block = ok_block };
92289224 }
92299225 }
test/cases/compile_errors/bad_panic_call_signature.zig+1-4
......@@ -15,8 +15,7 @@ pub const panic = struct {
1515 pub const castToNull = simple_panic.castToNull;
1616 pub const incorrectAlignment = simple_panic.incorrectAlignment;
1717 pub const invalidErrorCode = simple_panic.invalidErrorCode;
18 pub const castTruncatedData = simple_panic.castTruncatedData;
19 pub const negativeToUnsigned = simple_panic.negativeToUnsigned;
18 pub const integerOutOfBounds = simple_panic.integerOutOfBounds;
2019 pub const integerOverflow = simple_panic.integerOverflow;
2120 pub const shlOverflow = simple_panic.shlOverflow;
2221 pub const shrOverflow = simple_panic.shrOverflow;
......@@ -27,8 +26,6 @@ pub const panic = struct {
2726 pub const shiftRhsTooBig = simple_panic.shiftRhsTooBig;
2827 pub const invalidEnumValue = simple_panic.invalidEnumValue;
2928 pub const forLenMismatch = simple_panic.forLenMismatch;
30 /// Delete after next zig1.wasm update
31 pub const memcpyLenMismatch = copyLenMismatch;
3229 pub const copyLenMismatch = simple_panic.copyLenMismatch;
3330 pub const memcpyAlias = simple_panic.memcpyAlias;
3431 pub const noreturnReturned = simple_panic.noreturnReturned;
test/cases/compile_errors/bad_panic_generic_signature.zig+1-4
......@@ -11,8 +11,7 @@ pub const panic = struct {
1111 pub const castToNull = simple_panic.castToNull;
1212 pub const incorrectAlignment = simple_panic.incorrectAlignment;
1313 pub const invalidErrorCode = simple_panic.invalidErrorCode;
14 pub const castTruncatedData = simple_panic.castTruncatedData;
15 pub const negativeToUnsigned = simple_panic.negativeToUnsigned;
14 pub const integerOutOfBounds = simple_panic.integerOutOfBounds;
1615 pub const integerOverflow = simple_panic.integerOverflow;
1716 pub const shlOverflow = simple_panic.shlOverflow;
1817 pub const shrOverflow = simple_panic.shrOverflow;
......@@ -23,8 +22,6 @@ pub const panic = struct {
2322 pub const shiftRhsTooBig = simple_panic.shiftRhsTooBig;
2423 pub const invalidEnumValue = simple_panic.invalidEnumValue;
2524 pub const forLenMismatch = simple_panic.forLenMismatch;
26 /// Delete after next zig1.wasm update
27 pub const memcpyLenMismatch = copyLenMismatch;
2825 pub const copyLenMismatch = simple_panic.copyLenMismatch;
2926 pub const memcpyAlias = simple_panic.memcpyAlias;
3027 pub const noreturnReturned = simple_panic.noreturnReturned;
test/cases/safety/@intCast to u0.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "integer cast truncated bits")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/signed integer not fitting in cast to unsigned integer - widening.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/signed integer not fitting in cast to unsigned integer.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/signed-unsigned vector cast.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/truncating vector cast.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "integer cast truncated bits")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/unsigned integer not fitting in cast to signed integer - same bit count.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "integer cast truncated bits")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/unsigned-signed vector cast.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "integer cast truncated bits")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/value does not fit in shortening cast - u0.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "integer cast truncated bits")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/cases/safety/value does not fit in shortening cast.zig +1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "integer cast truncated bits")) {
5 if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
test/incremental/change_panic_handler_explicit+3-12
......@@ -26,8 +26,7 @@ pub const panic = struct {
2626 pub const castToNull = no_panic.castToNull;
2727 pub const incorrectAlignment = no_panic.incorrectAlignment;
2828 pub const invalidErrorCode = no_panic.invalidErrorCode;
29 pub const castTruncatedData = no_panic.castTruncatedData;
30 pub const negativeToUnsigned = no_panic.negativeToUnsigned;
29 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
3130 pub const shlOverflow = no_panic.shlOverflow;
3231 pub const shrOverflow = no_panic.shrOverflow;
3332 pub const divideByZero = no_panic.divideByZero;
......@@ -37,8 +36,6 @@ pub const panic = struct {
3736 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
3837 pub const invalidEnumValue = no_panic.invalidEnumValue;
3938 pub const forLenMismatch = no_panic.forLenMismatch;
40 /// Delete after next zig1.wasm update
41 pub const memcpyLenMismatch = copyLenMismatch;
4239 pub const copyLenMismatch = no_panic.copyLenMismatch;
4340 pub const memcpyAlias = no_panic.memcpyAlias;
4441 pub const noreturnReturned = no_panic.noreturnReturned;
......@@ -75,8 +72,7 @@ pub const panic = struct {
7572 pub const castToNull = no_panic.castToNull;
7673 pub const incorrectAlignment = no_panic.incorrectAlignment;
7774 pub const invalidErrorCode = no_panic.invalidErrorCode;
78 pub const castTruncatedData = no_panic.castTruncatedData;
79 pub const negativeToUnsigned = no_panic.negativeToUnsigned;
75 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
8076 pub const shlOverflow = no_panic.shlOverflow;
8177 pub const shrOverflow = no_panic.shrOverflow;
8278 pub const divideByZero = no_panic.divideByZero;
......@@ -86,8 +82,6 @@ pub const panic = struct {
8682 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
8783 pub const invalidEnumValue = no_panic.invalidEnumValue;
8884 pub const forLenMismatch = no_panic.forLenMismatch;
89 /// Delete after next zig1.wasm update
90 pub const memcpyLenMismatch = copyLenMismatch;
9185 pub const copyLenMismatch = no_panic.copyLenMismatch;
9286 pub const memcpyAlias = no_panic.memcpyAlias;
9387 pub const noreturnReturned = no_panic.noreturnReturned;
......@@ -124,8 +118,7 @@ pub const panic = struct {
124118 pub const castToNull = no_panic.castToNull;
125119 pub const incorrectAlignment = no_panic.incorrectAlignment;
126120 pub const invalidErrorCode = no_panic.invalidErrorCode;
127 pub const castTruncatedData = no_panic.castTruncatedData;
128 pub const negativeToUnsigned = no_panic.negativeToUnsigned;
121 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
129122 pub const shlOverflow = no_panic.shlOverflow;
130123 pub const shrOverflow = no_panic.shrOverflow;
131124 pub const divideByZero = no_panic.divideByZero;
......@@ -135,8 +128,6 @@ pub const panic = struct {
135128 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
136129 pub const invalidEnumValue = no_panic.invalidEnumValue;
137130 pub const forLenMismatch = no_panic.forLenMismatch;
138 /// Delete after next zig1.wasm update
139 pub const memcpyLenMismatch = copyLenMismatch;
140131 pub const copyLenMismatch = no_panic.copyLenMismatch;
141132 pub const memcpyAlias = no_panic.memcpyAlias;
142133 pub const noreturnReturned = no_panic.noreturnReturned;