authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-29 18:45:08+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-30 14:47:59+00:00
logb01d6b156cf4d273be40a6e6288f4766f71f4a29
tree084bfcf9ad8dfbce7879f750383a3091f699f52f
parentc5e34df555a98c51919eb45b0ab2a5ee3b37f4bf

compiler: add `intcast_safe` AIR instruction

This instruction is like `intcast`, but includes two safety checks: * Checks that the int is in range of the destination type * If the destination type is an exhaustive enum, checks that the int is a named enum value This instruction is locked behind the `safety_checked_instructions` backend feature; if unsupported, Sema will emit a fallback, as with other safety-checked instructions. This instruction is used to add a missing safety check for `@enumFromInt` truncating bits. This check also has a fallback for backends which do not yet support `safety_checked_instructions`. Resolves: #21946

17 files changed, 249 insertions(+), 29 deletions(-)

src/Air.zig+8
......@@ -574,6 +574,12 @@ pub const Inst = struct {
574574 /// See `trunc` for integer truncation.
575575 /// Uses the `ty_op` field.
576576 intcast,
577 /// Like `intcast`, but includes two safety checks:
578 /// * triggers a safety panic if the cast truncates bits
579 /// * triggers a safety panic if the destination type is an exhaustive enum
580 /// and the operand is not a valid value of this type; i.e. equivalent to
581 /// a safety check based on `.is_named_enum_value`
582 intcast_safe,
577583 /// Truncate higher bits from an integer, resulting in an integer with the same
578584 /// sign but an equal or smaller number of bits.
579585 /// Uses the `ty_op` field.
......@@ -1463,6 +1469,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
14631469 .fpext,
14641470 .fptrunc,
14651471 .intcast,
1472 .intcast_safe,
14661473 .trunc,
14671474 .optional_payload,
14681475 .optional_payload_ptr,
......@@ -1712,6 +1719,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
17121719 .add_safe,
17131720 .sub_safe,
17141721 .mul_safe,
1722 .intcast_safe,
17151723 => true,
17161724
17171725 .add,
src/Air/types_resolved.zig+1
......@@ -104,6 +104,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
104104 .fptrunc,
105105 .fpext,
106106 .intcast,
107 .intcast_safe,
107108 .trunc,
108109 .optional_payload,
109110 .optional_payload_ptr,
src/Liveness.zig+2
......@@ -345,6 +345,7 @@ pub fn categorizeOperand(
345345 .fpext,
346346 .fptrunc,
347347 .intcast,
348 .intcast_safe,
348349 .trunc,
349350 .optional_payload,
350351 .optional_payload_ptr,
......@@ -977,6 +978,7 @@ fn analyzeInst(
977978 .fpext,
978979 .fptrunc,
979980 .intcast,
981 .intcast_safe,
980982 .trunc,
981983 .optional_payload,
982984 .optional_payload_ptr,
src/Liveness/Verify.zig+1
......@@ -81,6 +81,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
8181 .fpext,
8282 .fptrunc,
8383 .intcast,
84 .intcast_safe,
8485 .trunc,
8586 .optional_payload,
8687 .optional_payload_ptr,
src/Sema.zig+43-20
......@@ -8798,11 +8798,12 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
87988798 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
87998799 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@enumFromInt");
88008800 const operand = try sema.resolveInst(extra.rhs);
8801 const operand_ty = sema.typeOf(operand);
88018802
88028803 if (dest_ty.zigTypeTag(zcu) != .@"enum") {
88038804 return sema.fail(block, src, "expected enum, found '{}'", .{dest_ty.fmt(pt)});
88048805 }
8805 _ = try sema.checkIntType(block, operand_src, sema.typeOf(operand));
8806 _ = try sema.checkIntType(block, operand_src, operand_ty);
88068807
88078808 if (try sema.resolveValue(operand)) |int_val| {
88088809 if (dest_ty.isNonexhaustiveEnum(zcu)) {
......@@ -8830,23 +8831,39 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
88308831 }
88318832
88328833 if (try sema.typeHasOnePossibleValue(dest_ty)) |opv| {
8833 const result = Air.internedToRef(opv.toIntern());
8834 // The operand is runtime-known but the result is comptime-known. In
8835 // this case we still need a safety check.
8836 // TODO add a safety check here. we can't use is_named_enum_value -
8837 // it needs to convert the enum back to int and make sure it equals the operand int.
8838 return result;
8834 if (block.wantSafety()) {
8835 // The operand is runtime-known but the result is comptime-known. In
8836 // this case we still need a safety check.
8837 const expect_int_val = switch (zcu.intern_pool.indexToKey(opv.toIntern())) {
8838 .enum_tag => |enum_tag| enum_tag.int,
8839 else => unreachable,
8840 };
8841 const expect_int_coerced = try pt.getCoerced(.fromInterned(expect_int_val), operand_ty);
8842 const ok = try block.addBinOp(.cmp_eq, operand, Air.internedToRef(expect_int_coerced.toIntern()));
8843 try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
8844 }
8845 return Air.internedToRef(opv.toIntern());
88398846 }
88408847
88418848 try sema.requireRuntimeBlock(block, src, operand_src);
8842 const result = try block.addTyOp(.intcast, dest_ty, operand);
8843 if (block.wantSafety() and !dest_ty.isNonexhaustiveEnum(zcu) and
8844 zcu.backendSupportsFeature(.is_named_enum_value))
8845 {
8846 const ok = try block.addUnOp(.is_named_enum_value, result);
8847 try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
8849 if (block.wantSafety()) {
8850 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
8851 _ = try sema.preparePanicId(src, .invalid_enum_value);
8852 return block.addTyOp(.intcast_safe, dest_ty, operand);
8853 } else {
8854 // Slightly silly fallback case...
8855 const int_tag_ty = dest_ty.intTagType(zcu);
8856 // Use `intCast`, since it'll set up the Sema-emitted safety checks for us!
8857 const int_val = try sema.intCast(block, src, int_tag_ty, src, operand, src, true, true);
8858 const result = try block.addBitCast(dest_ty, int_val);
8859 if (zcu.backendSupportsFeature(.is_named_enum_value)) {
8860 const ok = try block.addUnOp(.is_named_enum_value, result);
8861 try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
8862 }
8863 return result;
8864 }
88488865 }
8849 return result;
8866 return block.addTyOp(.intcast, dest_ty, operand);
88508867}
88518868
88528869/// Pointer in, pointer out.
......@@ -10192,7 +10209,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
1019210209 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast");
1019310210 const operand = try sema.resolveInst(extra.rhs);
1019410211
10195 return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src, true);
10212 return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src, true, false);
1019610213}
1019710214
1019810215fn intCast(
......@@ -10204,6 +10221,7 @@ fn intCast(
1020410221 operand: Air.Inst.Ref,
1020510222 operand_src: LazySrcLoc,
1020610223 runtime_safety: bool,
10224 safety_panics_are_enum: bool,
1020710225) CompileError!Air.Inst.Ref {
1020810226 const pt = sema.pt;
1020910227 const zcu = pt.zcu;
......@@ -10242,7 +10260,7 @@ fn intCast(
1024210260 const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst);
1024310261 break :ok is_in_range;
1024410262 };
10245 try sema.addSafetyCheck(block, src, ok, .cast_truncated_data);
10263 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
1024610264 }
1024710265 }
1024810266
......@@ -10251,6 +10269,11 @@ fn intCast(
1025110269
1025210270 try sema.requireRuntimeBlock(block, src, operand_src);
1025310271 if (runtime_safety and block.wantSafety()) {
10272 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
10273 _ = try sema.preparePanicId(src, .negative_to_unsigned);
10274 _ = try sema.preparePanicId(src, .cast_truncated_data);
10275 return block.addTyOp(.intcast_safe, dest_ty, operand);
10276 }
1025410277 const actual_info = operand_scalar_ty.intInfo(zcu);
1025510278 const wanted_info = dest_scalar_ty.intInfo(zcu);
1025610279 const actual_bits = actual_info.bits;
......@@ -10305,7 +10328,7 @@ fn intCast(
1030510328 break :ok is_in_range;
1030610329 };
1030710330 // TODO negative_to_unsigned?
10308 try sema.addSafetyCheck(block, src, ok, .cast_truncated_data);
10331 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
1030910332 } else {
1031010333 const ok = if (is_vector) ok: {
1031110334 const is_in_range = try block.addCmpVector(operand, dest_max, .lte);
......@@ -10321,7 +10344,7 @@ fn intCast(
1032110344 const is_in_range = try block.addBinOp(.cmp_lte, operand, dest_max);
1032210345 break :ok is_in_range;
1032310346 };
10324 try sema.addSafetyCheck(block, src, ok, .cast_truncated_data);
10347 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
1032510348 }
1032610349 } else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) {
1032710350 // no shrinkage, yes sign loss
......@@ -10344,7 +10367,7 @@ fn intCast(
1034410367 const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst);
1034510368 break :ok is_in_range;
1034610369 };
10347 try sema.addSafetyCheck(block, src, ok, .negative_to_unsigned);
10370 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .negative_to_unsigned);
1034810371 }
1034910372 }
1035010373 return block.addTyOp(.intcast, dest_ty, operand);
......@@ -14149,7 +14172,7 @@ fn zirShl(
1414914172 {
1415014173 const max_int = Air.internedToRef((try lhs_ty.maxInt(pt, lhs_ty)).toIntern());
1415114174 const rhs_limited = try sema.analyzeMinMax(block, rhs_src, .min, &.{ rhs, max_int }, &.{ rhs_src, rhs_src });
14152 break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false);
14175 break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false, false);
1415314176 } else {
1415414177 break :rhs rhs;
1415514178 }
src/Zcu.zig+2-1
......@@ -3313,7 +3313,7 @@ pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void {
33133313
33143314pub const Feature = enum {
33153315 /// When this feature is enabled, Sema will emit calls to
3316 /// `std.builtin.Panic` functions for things like safety checks and
3316 /// `std.builtin.panic` functions for things like safety checks and
33173317 /// unreachables. Otherwise traps will be emitted.
33183318 panic_fn,
33193319 /// When this feature is enabled, Sema will insert tracer functions for gathering a stack
......@@ -3329,6 +3329,7 @@ pub const Feature = enum {
33293329 /// * `Air.Inst.Tag.add_safe`
33303330 /// * `Air.Inst.Tag.sub_safe`
33313331 /// * `Air.Inst.Tag.mul_safe`
3332 /// * `Air.Inst.Tag.intcast_safe`
33323333 /// The motivation for this feature is that it makes AIR smaller, and makes it easier
33333334 /// to generate better machine code in the backends. All backends should migrate to
33343335 /// enabling this feature.
src/arch/aarch64/CodeGen.zig+1
......@@ -871,6 +871,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
871871 .add_safe,
872872 .sub_safe,
873873 .mul_safe,
874 .intcast_safe,
874875 => return self.fail("TODO implement safety_checked_instructions", .{}),
875876
876877 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/arm/CodeGen.zig+1
......@@ -860,6 +860,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
860860 .add_safe,
861861 .sub_safe,
862862 .mul_safe,
863 .intcast_safe,
863864 => return self.fail("TODO implement safety_checked_instructions", .{}),
864865
865866 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/riscv64/CodeGen.zig+1
......@@ -1517,6 +1517,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
15171517 .add_safe,
15181518 .sub_safe,
15191519 .mul_safe,
1520 .intcast_safe,
15201521 => return func.fail("TODO implement safety_checked_instructions", .{}),
15211522
15221523 .cmp_lt,
src/arch/sparc64/CodeGen.zig+1
......@@ -714,6 +714,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
714714 .add_safe,
715715 .sub_safe,
716716 .mul_safe,
717 .intcast_safe,
717718 => @panic("TODO implement safety_checked_instructions"),
718719
719720 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),
src/arch/wasm/CodeGen.zig+1
......@@ -2084,6 +2084,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
20842084 .add_safe,
20852085 .sub_safe,
20862086 .mul_safe,
2087 .intcast_safe,
20872088 => return cg.fail("TODO implement safety_checked_instructions", .{}),
20882089
20892090 .work_item_id,
src/arch/x86_64/CodeGen.zig+1
......@@ -2549,6 +2549,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
25492549 .add_safe,
25502550 .sub_safe,
25512551 .mul_safe,
2552 .intcast_safe,
25522553 => return cg.fail("TODO implement safety_checked_instructions", .{}),
25532554
25542555 .add_optimized => try cg.airBinOp(inst, .add),
src/codegen/c.zig+1
......@@ -3436,6 +3436,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
34363436 .add_safe,
34373437 .sub_safe,
34383438 .mul_safe,
3439 .intcast_safe,
34393440 => return f.fail("TODO implement safety_checked_instructions", .{}),
34403441
34413442 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
src/codegen/llvm.zig+138-8
......@@ -5162,7 +5162,8 @@ pub const FuncGen = struct {
51625162 .try_cold => try self.airTry(body[i..], true),
51635163 .try_ptr => try self.airTryPtr(inst, false),
51645164 .try_ptr_cold => try self.airTryPtr(inst, true),
5165 .intcast => try self.airIntCast(inst),
5165 .intcast => try self.airIntCast(inst, false),
5166 .intcast_safe => try self.airIntCast(inst, true),
51665167 .trunc => try self.airTrunc(inst),
51675168 .fptrunc => try self.airFptrunc(inst),
51685169 .fpext => try self.airFpext(inst),
......@@ -9246,20 +9247,110 @@ pub const FuncGen = struct {
92469247 }
92479248 }
92489249
9249 fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
9250 const o = self.ng.object;
9250 fn airIntCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) !Builder.Value {
9251 const o = fg.ng.object;
92519252 const zcu = o.pt.zcu;
9252 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
9253 const dest_ty = self.typeOfIndex(inst);
9253 const ty_op = fg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
9254 const dest_ty = fg.typeOfIndex(inst);
92549255 const dest_llvm_ty = try o.lowerType(dest_ty);
9255 const operand = try self.resolveInst(ty_op.operand);
9256 const operand_ty = self.typeOf(ty_op.operand);
9256 const operand = try fg.resolveInst(ty_op.operand);
9257 const operand_ty = fg.typeOf(ty_op.operand);
92579258 const operand_info = operand_ty.intInfo(zcu);
92589259
9259 return self.wip.conv(switch (operand_info.signedness) {
9260 const dest_is_enum = dest_ty.zigTypeTag(zcu) == .@"enum";
9261
9262 safety: {
9263 if (!safety) break :safety;
9264 const dest_scalar = dest_ty.scalarType(zcu);
9265 const operand_scalar = operand_ty.scalarType(zcu);
9266
9267 const dest_info = dest_ty.intInfo(zcu);
9268
9269 const have_min_check, const have_max_check = c: {
9270 const dest_pos_bits = dest_info.bits - @intFromBool(dest_info.signedness == .signed);
9271 const operand_pos_bits = operand_info.bits - @intFromBool(operand_info.signedness == .signed);
9272
9273 const dest_allows_neg = dest_info.signedness == .signed and dest_info.bits > 0;
9274 const operand_maybe_neg = operand_info.signedness == .signed and operand_info.bits > 0;
9275
9276 break :c .{
9277 operand_maybe_neg and (!dest_allows_neg or dest_info.bits < operand_info.bits),
9278 dest_pos_bits < operand_pos_bits,
9279 };
9280 };
9281
9282 if (!have_min_check and !have_max_check) break :safety;
9283
9284 const operand_llvm_ty = try o.lowerType(operand_ty);
9285 const operand_scalar_llvm_ty = try o.lowerType(operand_scalar);
9286
9287 const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
9288 assert(is_vector == (dest_ty.zigTypeTag(zcu) == .vector));
9289
9290 const min_panic_id: Zcu.SimplePanicId, const max_panic_id: Zcu.SimplePanicId = id: {
9291 if (dest_is_enum) break :id .{ .invalid_enum_value, .invalid_enum_value };
9292 if (dest_info.signedness == .unsigned) break :id .{ .negative_to_unsigned, .cast_truncated_data };
9293 break :id .{ .cast_truncated_data, .cast_truncated_data };
9294 };
9295
9296 if (have_min_check) {
9297 const min_const_scalar = try minIntConst(&o.builder, dest_scalar, operand_scalar_llvm_ty, zcu);
9298 const min_val = if (is_vector) try o.builder.splatValue(operand_llvm_ty, min_const_scalar) else min_const_scalar.toValue();
9299 const ok_maybe_vec = try fg.cmp(.normal, .gte, operand_ty, operand, min_val);
9300 const ok = if (is_vector) ok: {
9301 const vec_ty = ok_maybe_vec.typeOfWip(&fg.wip);
9302 break :ok try fg.wip.callIntrinsic(.normal, .none, .@"vector.reduce.and", &.{vec_ty}, &.{ok_maybe_vec}, "");
9303 } else ok_maybe_vec;
9304 const fail_block = try fg.wip.block(1, "IntMinFail");
9305 const ok_block = try fg.wip.block(1, "IntMinOk");
9306 _ = try fg.wip.brCond(ok, ok_block, fail_block, .none);
9307 fg.wip.cursor = .{ .block = fail_block };
9308 try fg.buildSimplePanic(min_panic_id);
9309 fg.wip.cursor = .{ .block = ok_block };
9310 }
9311
9312 if (have_max_check) {
9313 const max_const_scalar = try maxIntConst(&o.builder, dest_scalar, operand_scalar_llvm_ty, zcu);
9314 const max_val = if (is_vector) try o.builder.splatValue(operand_llvm_ty, max_const_scalar) else max_const_scalar.toValue();
9315 const ok_maybe_vec = try fg.cmp(.normal, .lte, operand_ty, operand, max_val);
9316 const ok = if (is_vector) ok: {
9317 const vec_ty = ok_maybe_vec.typeOfWip(&fg.wip);
9318 break :ok try fg.wip.callIntrinsic(.normal, .none, .@"vector.reduce.and", &.{vec_ty}, &.{ok_maybe_vec}, "");
9319 } else ok_maybe_vec;
9320 const fail_block = try fg.wip.block(1, "IntMaxFail");
9321 const ok_block = try fg.wip.block(1, "IntMaxOk");
9322 _ = try fg.wip.brCond(ok, ok_block, fail_block, .none);
9323 fg.wip.cursor = .{ .block = fail_block };
9324 try fg.buildSimplePanic(max_panic_id);
9325 fg.wip.cursor = .{ .block = ok_block };
9326 }
9327 }
9328
9329 const result = try fg.wip.conv(switch (operand_info.signedness) {
92609330 .signed => .signed,
92619331 .unsigned => .unsigned,
92629332 }, operand, dest_llvm_ty, "");
9333
9334 if (safety and dest_is_enum and !dest_ty.isNonexhaustiveEnum(zcu)) {
9335 const llvm_fn = try fg.getIsNamedEnumValueFunction(dest_ty);
9336 const is_valid_enum_val = try fg.wip.call(
9337 .normal,
9338 .fastcc,
9339 .none,
9340 llvm_fn.typeOf(&o.builder),
9341 llvm_fn.toValue(&o.builder),
9342 &.{result},
9343 "",
9344 );
9345 const fail_block = try fg.wip.block(1, "ValidEnumFail");
9346 const ok_block = try fg.wip.block(1, "ValidEnumOk");
9347 _ = try fg.wip.brCond(is_valid_enum_val, ok_block, fail_block, .none);
9348 fg.wip.cursor = .{ .block = fail_block };
9349 try fg.buildSimplePanic(.invalid_enum_value);
9350 fg.wip.cursor = .{ .block = ok_block };
9351 }
9352
9353 return result;
92639354 }
92649355
92659356 fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
......@@ -12953,3 +13044,42 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
1295313044 => unreachable,
1295413045 }
1295513046}
13047
13048fn minIntConst(b: *Builder, min_ty: Type, as_ty: Builder.Type, zcu: *const Zcu) Allocator.Error!Builder.Constant {
13049 const info = min_ty.intInfo(zcu);
13050 if (info.signedness == .unsigned or info.bits == 0) {
13051 return b.intConst(as_ty, 0);
13052 }
13053 if (std.math.cast(u6, info.bits - 1)) |shift| {
13054 const min_val: i64 = @as(i64, std.math.minInt(i64)) >> (63 - shift);
13055 return b.intConst(as_ty, min_val);
13056 }
13057 var res: std.math.big.int.Managed = try .init(zcu.gpa);
13058 defer res.deinit();
13059 try res.setTwosCompIntLimit(.min, info.signedness, info.bits);
13060 return b.bigIntConst(as_ty, res.toConst());
13061}
13062
13063fn maxIntConst(b: *Builder, max_ty: Type, as_ty: Builder.Type, zcu: *const Zcu) Allocator.Error!Builder.Constant {
13064 const info = max_ty.intInfo(zcu);
13065 switch (info.bits) {
13066 0 => return b.intConst(as_ty, 0),
13067 1 => switch (info.signedness) {
13068 .signed => return b.intConst(as_ty, 0),
13069 .unsigned => return b.intConst(as_ty, 1),
13070 },
13071 else => {},
13072 }
13073 const unsigned_bits = switch (info.signedness) {
13074 .unsigned => info.bits,
13075 .signed => info.bits - 1,
13076 };
13077 if (std.math.cast(u6, unsigned_bits)) |shift| {
13078 const max_val: u64 = (@as(u64, 1) << shift) - 1;
13079 return b.intConst(as_ty, max_val);
13080 }
13081 var res: std.math.big.int.Managed = try .init(zcu.gpa);
13082 defer res.deinit();
13083 try res.setTwosCompIntLimit(.max, info.signedness, info.bits);
13084 return b.bigIntConst(as_ty, res.toConst());
13085}
src/print_air.zig+1
......@@ -223,6 +223,7 @@ const Writer = struct {
223223 .fptrunc,
224224 .fpext,
225225 .intcast,
226 .intcast_safe,
226227 .trunc,
227228 .optional_payload,
228229 .optional_payload_ptr,
test/cases/safety/@enumFromInt truncated bits - exhaustive.zig created+23
......@@ -0,0 +1,23 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 if (std.mem.eql(u8, message, "invalid enum value")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9
10pub fn main() u8 {
11 var num: i5 = undefined;
12 num = 14;
13
14 const E = enum(u3) { a, b, c, d, e, f, g, h };
15 const invalid: E = @enumFromInt(num);
16 _ = invalid;
17
18 return 1;
19}
20
21// run
22// backend=llvm
23// target=native
test/cases/safety/@enumFromInt truncated bits - nonexhaustive.zig created+23
......@@ -0,0 +1,23 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 if (std.mem.eql(u8, message, "invalid enum value")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9
10pub fn main() u8 {
11 var num: u8 = undefined;
12 num = 250;
13
14 const E = enum(u6) { _ };
15 const invalid: E = @enumFromInt(num);
16 _ = invalid;
17
18 return 1;
19}
20
21// run
22// backend=llvm
23// target=native