From b01d6b156cf4d273be40a6e6288f4766f71f4a29 Mon Sep 17 00:00:00 2001 From: mlugg Date: Wed, 29 Jan 2025 18:45:08 +0000 Subject: [PATCH] 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 --- src/Air.zig | 8 + src/Air/types_resolved.zig | 1 + src/Liveness.zig | 2 + src/Liveness/Verify.zig | 1 + src/Sema.zig | 63 +++++--- src/Zcu.zig | 3 +- src/arch/aarch64/CodeGen.zig | 1 + src/arch/arm/CodeGen.zig | 1 + src/arch/riscv64/CodeGen.zig | 1 + src/arch/sparc64/CodeGen.zig | 1 + src/arch/wasm/CodeGen.zig | 1 + src/arch/x86_64/CodeGen.zig | 1 + src/codegen/c.zig | 1 + src/codegen/llvm.zig | 146 +++++++++++++++++- src/print_air.zig | 1 + ...numFromInt truncated bits - exhaustive.zig | 23 +++ ...FromInt truncated bits - nonexhaustive.zig | 23 +++ 17 files changed, 249 insertions(+), 29 deletions(-) create mode 100644 test/cases/safety/@enumFromInt truncated bits - exhaustive.zig create mode 100644 test/cases/safety/@enumFromInt truncated bits - nonexhaustive.zig diff --git a/src/Air.zig b/src/Air.zig index 98f1ba8c71..30ea7b0abc 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -574,6 +574,12 @@ pub const Inst = struct { /// See `trunc` for integer truncation. /// Uses the `ty_op` field. intcast, + /// Like `intcast`, but includes two safety checks: + /// * triggers a safety panic if the cast truncates bits + /// * triggers a safety panic if the destination type is an exhaustive enum + /// and the operand is not a valid value of this type; i.e. equivalent to + /// a safety check based on `.is_named_enum_value` + intcast_safe, /// Truncate higher bits from an integer, resulting in an integer with the same /// sign but an equal or smaller number of bits. /// Uses the `ty_op` field. @@ -1463,6 +1469,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) .fpext, .fptrunc, .intcast, + .intcast_safe, .trunc, .optional_payload, .optional_payload_ptr, @@ -1712,6 +1719,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => true, .add, diff --git a/src/Air/types_resolved.zig b/src/Air/types_resolved.zig index b627ad388b..ead34cb383 100644 --- a/src/Air/types_resolved.zig +++ b/src/Air/types_resolved.zig @@ -104,6 +104,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { .fptrunc, .fpext, .intcast, + .intcast_safe, .trunc, .optional_payload, .optional_payload_ptr, diff --git a/src/Liveness.zig b/src/Liveness.zig index e6e9c07363..28102a9c45 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -345,6 +345,7 @@ pub fn categorizeOperand( .fpext, .fptrunc, .intcast, + .intcast_safe, .trunc, .optional_payload, .optional_payload_ptr, @@ -977,6 +978,7 @@ fn analyzeInst( .fpext, .fptrunc, .intcast, + .intcast_safe, .trunc, .optional_payload, .optional_payload_ptr, diff --git a/src/Liveness/Verify.zig b/src/Liveness/Verify.zig index 01e0842ded..583c6d46bb 100644 --- a/src/Liveness/Verify.zig +++ b/src/Liveness/Verify.zig @@ -81,6 +81,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { .fpext, .fptrunc, .intcast, + .intcast_safe, .trunc, .optional_payload, .optional_payload_ptr, diff --git a/src/Sema.zig b/src/Sema.zig index 2fe4aac581..bc9d5ea55f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8798,11 +8798,12 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@enumFromInt"); const operand = try sema.resolveInst(extra.rhs); + const operand_ty = sema.typeOf(operand); if (dest_ty.zigTypeTag(zcu) != .@"enum") { return sema.fail(block, src, "expected enum, found '{}'", .{dest_ty.fmt(pt)}); } - _ = try sema.checkIntType(block, operand_src, sema.typeOf(operand)); + _ = try sema.checkIntType(block, operand_src, operand_ty); if (try sema.resolveValue(operand)) |int_val| { if (dest_ty.isNonexhaustiveEnum(zcu)) { @@ -8830,23 +8831,39 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } if (try sema.typeHasOnePossibleValue(dest_ty)) |opv| { - const result = Air.internedToRef(opv.toIntern()); - // The operand is runtime-known but the result is comptime-known. In - // this case we still need a safety check. - // TODO add a safety check here. we can't use is_named_enum_value - - // it needs to convert the enum back to int and make sure it equals the operand int. - return result; + if (block.wantSafety()) { + // The operand is runtime-known but the result is comptime-known. In + // this case we still need a safety check. + const expect_int_val = switch (zcu.intern_pool.indexToKey(opv.toIntern())) { + .enum_tag => |enum_tag| enum_tag.int, + else => unreachable, + }; + const expect_int_coerced = try pt.getCoerced(.fromInterned(expect_int_val), operand_ty); + const ok = try block.addBinOp(.cmp_eq, operand, Air.internedToRef(expect_int_coerced.toIntern())); + try sema.addSafetyCheck(block, src, ok, .invalid_enum_value); + } + return Air.internedToRef(opv.toIntern()); } try sema.requireRuntimeBlock(block, src, operand_src); - const result = try block.addTyOp(.intcast, dest_ty, operand); - if (block.wantSafety() and !dest_ty.isNonexhaustiveEnum(zcu) and - zcu.backendSupportsFeature(.is_named_enum_value)) - { - const ok = try block.addUnOp(.is_named_enum_value, result); - try sema.addSafetyCheck(block, src, ok, .invalid_enum_value); + if (block.wantSafety()) { + if (zcu.backendSupportsFeature(.safety_checked_instructions)) { + _ = try sema.preparePanicId(src, .invalid_enum_value); + return block.addTyOp(.intcast_safe, dest_ty, operand); + } else { + // Slightly silly fallback case... + const int_tag_ty = dest_ty.intTagType(zcu); + // Use `intCast`, since it'll set up the Sema-emitted safety checks for us! + const int_val = try sema.intCast(block, src, int_tag_ty, src, operand, src, true, true); + const result = try block.addBitCast(dest_ty, int_val); + if (zcu.backendSupportsFeature(.is_named_enum_value)) { + const ok = try block.addUnOp(.is_named_enum_value, result); + try sema.addSafetyCheck(block, src, ok, .invalid_enum_value); + } + return result; + } } - return result; + return block.addTyOp(.intcast, dest_ty, operand); } /// Pointer in, pointer out. @@ -10192,7 +10209,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast"); const operand = try sema.resolveInst(extra.rhs); - return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src, true); + return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src, true, false); } fn intCast( @@ -10204,6 +10221,7 @@ fn intCast( operand: Air.Inst.Ref, operand_src: LazySrcLoc, runtime_safety: bool, + safety_panics_are_enum: bool, ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; @@ -10242,7 +10260,7 @@ fn intCast( const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst); break :ok is_in_range; }; - try sema.addSafetyCheck(block, src, ok, .cast_truncated_data); + try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data); } } @@ -10251,6 +10269,11 @@ fn intCast( try sema.requireRuntimeBlock(block, src, operand_src); if (runtime_safety and block.wantSafety()) { + if (zcu.backendSupportsFeature(.safety_checked_instructions)) { + _ = try sema.preparePanicId(src, .negative_to_unsigned); + _ = try sema.preparePanicId(src, .cast_truncated_data); + return block.addTyOp(.intcast_safe, dest_ty, operand); + } const actual_info = operand_scalar_ty.intInfo(zcu); const wanted_info = dest_scalar_ty.intInfo(zcu); const actual_bits = actual_info.bits; @@ -10305,7 +10328,7 @@ fn intCast( break :ok is_in_range; }; // TODO negative_to_unsigned? - try sema.addSafetyCheck(block, src, ok, .cast_truncated_data); + try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data); } else { const ok = if (is_vector) ok: { const is_in_range = try block.addCmpVector(operand, dest_max, .lte); @@ -10321,7 +10344,7 @@ fn intCast( const is_in_range = try block.addBinOp(.cmp_lte, operand, dest_max); break :ok is_in_range; }; - try sema.addSafetyCheck(block, src, ok, .cast_truncated_data); + try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data); } } else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) { // no shrinkage, yes sign loss @@ -10344,7 +10367,7 @@ fn intCast( const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst); break :ok is_in_range; }; - try sema.addSafetyCheck(block, src, ok, .negative_to_unsigned); + try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .negative_to_unsigned); } } return block.addTyOp(.intcast, dest_ty, operand); @@ -14149,7 +14172,7 @@ fn zirShl( { const max_int = Air.internedToRef((try lhs_ty.maxInt(pt, lhs_ty)).toIntern()); const rhs_limited = try sema.analyzeMinMax(block, rhs_src, .min, &.{ rhs, max_int }, &.{ rhs_src, rhs_src }); - break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false); + break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false, false); } else { break :rhs rhs; } diff --git a/src/Zcu.zig b/src/Zcu.zig index 0f279440b2..13b302180d 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3313,7 +3313,7 @@ pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void { pub const Feature = enum { /// When this feature is enabled, Sema will emit calls to - /// `std.builtin.Panic` functions for things like safety checks and + /// `std.builtin.panic` functions for things like safety checks and /// unreachables. Otherwise traps will be emitted. panic_fn, /// When this feature is enabled, Sema will insert tracer functions for gathering a stack @@ -3329,6 +3329,7 @@ pub const Feature = enum { /// * `Air.Inst.Tag.add_safe` /// * `Air.Inst.Tag.sub_safe` /// * `Air.Inst.Tag.mul_safe` + /// * `Air.Inst.Tag.intcast_safe` /// The motivation for this feature is that it makes AIR smaller, and makes it easier /// to generate better machine code in the backends. All backends should migrate to /// enabling this feature. diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 64230dfc9e..9ff240259c 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -871,6 +871,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => return self.fail("TODO implement safety_checked_instructions", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index fa892117f3..fc7335b4c6 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -860,6 +860,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => return self.fail("TODO implement safety_checked_instructions", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 66f4ce6f0d..de0f9836b2 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1517,6 +1517,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => return func.fail("TODO implement safety_checked_instructions", .{}), .cmp_lt, diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 240123ee51..626914c38b 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -714,6 +714,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => @panic("TODO implement safety_checked_instructions"), .is_named_enum_value => @panic("TODO implement is_named_enum_value"), diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 51019969f6..ecce271459 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -2084,6 +2084,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => return cg.fail("TODO implement safety_checked_instructions", .{}), .work_item_id, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index e91681516a..fc3dbcb23d 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2549,6 +2549,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => return cg.fail("TODO implement safety_checked_instructions", .{}), .add_optimized => try cg.airBinOp(inst, .add), diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 6244bd2324..241f215875 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3436,6 +3436,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .add_safe, .sub_safe, .mul_safe, + .intcast_safe, => return f.fail("TODO implement safety_checked_instructions", .{}), .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 17d93ebcbf..7ca35d149b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5162,7 +5162,8 @@ pub const FuncGen = struct { .try_cold => try self.airTry(body[i..], true), .try_ptr => try self.airTryPtr(inst, false), .try_ptr_cold => try self.airTryPtr(inst, true), - .intcast => try self.airIntCast(inst), + .intcast => try self.airIntCast(inst, false), + .intcast_safe => try self.airIntCast(inst, true), .trunc => try self.airTrunc(inst), .fptrunc => try self.airFptrunc(inst), .fpext => try self.airFpext(inst), @@ -9246,20 +9247,110 @@ pub const FuncGen = struct { } } - fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { - const o = self.ng.object; + fn airIntCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) !Builder.Value { + const o = fg.ng.object; const zcu = o.pt.zcu; - const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; - const dest_ty = self.typeOfIndex(inst); + const ty_op = fg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; + const dest_ty = fg.typeOfIndex(inst); const dest_llvm_ty = try o.lowerType(dest_ty); - const operand = try self.resolveInst(ty_op.operand); - const operand_ty = self.typeOf(ty_op.operand); + const operand = try fg.resolveInst(ty_op.operand); + const operand_ty = fg.typeOf(ty_op.operand); const operand_info = operand_ty.intInfo(zcu); - return self.wip.conv(switch (operand_info.signedness) { + const dest_is_enum = dest_ty.zigTypeTag(zcu) == .@"enum"; + + safety: { + if (!safety) break :safety; + const dest_scalar = dest_ty.scalarType(zcu); + const operand_scalar = operand_ty.scalarType(zcu); + + const dest_info = dest_ty.intInfo(zcu); + + const have_min_check, const have_max_check = c: { + const dest_pos_bits = dest_info.bits - @intFromBool(dest_info.signedness == .signed); + const operand_pos_bits = operand_info.bits - @intFromBool(operand_info.signedness == .signed); + + const dest_allows_neg = dest_info.signedness == .signed and dest_info.bits > 0; + const operand_maybe_neg = operand_info.signedness == .signed and operand_info.bits > 0; + + break :c .{ + operand_maybe_neg and (!dest_allows_neg or dest_info.bits < operand_info.bits), + dest_pos_bits < operand_pos_bits, + }; + }; + + if (!have_min_check and !have_max_check) break :safety; + + const operand_llvm_ty = try o.lowerType(operand_ty); + const operand_scalar_llvm_ty = try o.lowerType(operand_scalar); + + const is_vector = operand_ty.zigTypeTag(zcu) == .vector; + assert(is_vector == (dest_ty.zigTypeTag(zcu) == .vector)); + + const min_panic_id: Zcu.SimplePanicId, const max_panic_id: Zcu.SimplePanicId = id: { + if (dest_is_enum) break :id .{ .invalid_enum_value, .invalid_enum_value }; + if (dest_info.signedness == .unsigned) break :id .{ .negative_to_unsigned, .cast_truncated_data }; + break :id .{ .cast_truncated_data, .cast_truncated_data }; + }; + + if (have_min_check) { + const min_const_scalar = try minIntConst(&o.builder, dest_scalar, operand_scalar_llvm_ty, zcu); + const min_val = if (is_vector) try o.builder.splatValue(operand_llvm_ty, min_const_scalar) else min_const_scalar.toValue(); + const ok_maybe_vec = try fg.cmp(.normal, .gte, operand_ty, operand, min_val); + const ok = if (is_vector) ok: { + const vec_ty = ok_maybe_vec.typeOfWip(&fg.wip); + break :ok try fg.wip.callIntrinsic(.normal, .none, .@"vector.reduce.and", &.{vec_ty}, &.{ok_maybe_vec}, ""); + } else ok_maybe_vec; + const fail_block = try fg.wip.block(1, "IntMinFail"); + const ok_block = try fg.wip.block(1, "IntMinOk"); + _ = try fg.wip.brCond(ok, ok_block, fail_block, .none); + fg.wip.cursor = .{ .block = fail_block }; + try fg.buildSimplePanic(min_panic_id); + fg.wip.cursor = .{ .block = ok_block }; + } + + if (have_max_check) { + const max_const_scalar = try maxIntConst(&o.builder, dest_scalar, operand_scalar_llvm_ty, zcu); + const max_val = if (is_vector) try o.builder.splatValue(operand_llvm_ty, max_const_scalar) else max_const_scalar.toValue(); + const ok_maybe_vec = try fg.cmp(.normal, .lte, operand_ty, operand, max_val); + const ok = if (is_vector) ok: { + const vec_ty = ok_maybe_vec.typeOfWip(&fg.wip); + break :ok try fg.wip.callIntrinsic(.normal, .none, .@"vector.reduce.and", &.{vec_ty}, &.{ok_maybe_vec}, ""); + } else ok_maybe_vec; + const fail_block = try fg.wip.block(1, "IntMaxFail"); + const ok_block = try fg.wip.block(1, "IntMaxOk"); + _ = try fg.wip.brCond(ok, ok_block, fail_block, .none); + fg.wip.cursor = .{ .block = fail_block }; + try fg.buildSimplePanic(max_panic_id); + fg.wip.cursor = .{ .block = ok_block }; + } + } + + const result = try fg.wip.conv(switch (operand_info.signedness) { .signed => .signed, .unsigned => .unsigned, }, operand, dest_llvm_ty, ""); + + if (safety and dest_is_enum and !dest_ty.isNonexhaustiveEnum(zcu)) { + const llvm_fn = try fg.getIsNamedEnumValueFunction(dest_ty); + const is_valid_enum_val = try fg.wip.call( + .normal, + .fastcc, + .none, + llvm_fn.typeOf(&o.builder), + llvm_fn.toValue(&o.builder), + &.{result}, + "", + ); + const fail_block = try fg.wip.block(1, "ValidEnumFail"); + const ok_block = try fg.wip.block(1, "ValidEnumOk"); + _ = try fg.wip.brCond(is_valid_enum_val, ok_block, fail_block, .none); + fg.wip.cursor = .{ .block = fail_block }; + try fg.buildSimplePanic(.invalid_enum_value); + fg.wip.cursor = .{ .block = ok_block }; + } + + return result; } fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { @@ -12953,3 +13044,42 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { => unreachable, } } + +fn minIntConst(b: *Builder, min_ty: Type, as_ty: Builder.Type, zcu: *const Zcu) Allocator.Error!Builder.Constant { + const info = min_ty.intInfo(zcu); + if (info.signedness == .unsigned or info.bits == 0) { + return b.intConst(as_ty, 0); + } + if (std.math.cast(u6, info.bits - 1)) |shift| { + const min_val: i64 = @as(i64, std.math.minInt(i64)) >> (63 - shift); + return b.intConst(as_ty, min_val); + } + var res: std.math.big.int.Managed = try .init(zcu.gpa); + defer res.deinit(); + try res.setTwosCompIntLimit(.min, info.signedness, info.bits); + return b.bigIntConst(as_ty, res.toConst()); +} + +fn maxIntConst(b: *Builder, max_ty: Type, as_ty: Builder.Type, zcu: *const Zcu) Allocator.Error!Builder.Constant { + const info = max_ty.intInfo(zcu); + switch (info.bits) { + 0 => return b.intConst(as_ty, 0), + 1 => switch (info.signedness) { + .signed => return b.intConst(as_ty, 0), + .unsigned => return b.intConst(as_ty, 1), + }, + else => {}, + } + const unsigned_bits = switch (info.signedness) { + .unsigned => info.bits, + .signed => info.bits - 1, + }; + if (std.math.cast(u6, unsigned_bits)) |shift| { + const max_val: u64 = (@as(u64, 1) << shift) - 1; + return b.intConst(as_ty, max_val); + } + var res: std.math.big.int.Managed = try .init(zcu.gpa); + defer res.deinit(); + try res.setTwosCompIntLimit(.max, info.signedness, info.bits); + return b.bigIntConst(as_ty, res.toConst()); +} diff --git a/src/print_air.zig b/src/print_air.zig index d99be7770d..d4cd20f3d4 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -223,6 +223,7 @@ const Writer = struct { .fptrunc, .fpext, .intcast, + .intcast_safe, .trunc, .optional_payload, .optional_payload_ptr, diff --git a/test/cases/safety/@enumFromInt truncated bits - exhaustive.zig b/test/cases/safety/@enumFromInt truncated bits - exhaustive.zig new file mode 100644 index 0000000000..e7a6b7e38b --- /dev/null +++ b/test/cases/safety/@enumFromInt truncated bits - exhaustive.zig @@ -0,0 +1,23 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { + if (std.mem.eql(u8, message, "invalid enum value")) { + std.process.exit(0); + } + std.process.exit(1); +} + +pub fn main() u8 { + var num: i5 = undefined; + num = 14; + + const E = enum(u3) { a, b, c, d, e, f, g, h }; + const invalid: E = @enumFromInt(num); + _ = invalid; + + return 1; +} + +// run +// backend=llvm +// target=native diff --git a/test/cases/safety/@enumFromInt truncated bits - nonexhaustive.zig b/test/cases/safety/@enumFromInt truncated bits - nonexhaustive.zig new file mode 100644 index 0000000000..240b937cf5 --- /dev/null +++ b/test/cases/safety/@enumFromInt truncated bits - nonexhaustive.zig @@ -0,0 +1,23 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { + if (std.mem.eql(u8, message, "invalid enum value")) { + std.process.exit(0); + } + std.process.exit(1); +} + +pub fn main() u8 { + var num: u8 = undefined; + num = 250; + + const E = enum(u6) { _ }; + const invalid: E = @enumFromInt(num); + _ = invalid; + + return 1; +} + +// run +// backend=llvm +// target=native