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 {...@@ -574,6 +574,12 @@ pub const Inst = struct {
574 /// See `trunc` for integer truncation.574 /// See `trunc` for integer truncation.
575 /// Uses the `ty_op` field.575 /// Uses the `ty_op` field.
576 intcast,576 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,
577 /// Truncate higher bits from an integer, resulting in an integer with the same583 /// Truncate higher bits from an integer, resulting in an integer with the same
578 /// sign but an equal or smaller number of bits.584 /// sign but an equal or smaller number of bits.
579 /// Uses the `ty_op` field.585 /// Uses the `ty_op` field.
...@@ -1463,6 +1469,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)...@@ -1463,6 +1469,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
1463 .fpext,1469 .fpext,
1464 .fptrunc,1470 .fptrunc,
1465 .intcast,1471 .intcast,
1472 .intcast_safe,
1466 .trunc,1473 .trunc,
1467 .optional_payload,1474 .optional_payload,
1468 .optional_payload_ptr,1475 .optional_payload_ptr,
...@@ -1712,6 +1719,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1712,6 +1719,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1712 .add_safe,1719 .add_safe,
1713 .sub_safe,1720 .sub_safe,
1714 .mul_safe,1721 .mul_safe,
1722 .intcast_safe,
1715 => true,1723 => true,
17161724
1717 .add,1725 .add,
src/Air/types_resolved.zig+1
...@@ -104,6 +104,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {...@@ -104,6 +104,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
104 .fptrunc,104 .fptrunc,
105 .fpext,105 .fpext,
106 .intcast,106 .intcast,
107 .intcast_safe,
107 .trunc,108 .trunc,
108 .optional_payload,109 .optional_payload,
109 .optional_payload_ptr,110 .optional_payload_ptr,
src/Liveness.zig+2
...@@ -345,6 +345,7 @@ pub fn categorizeOperand(...@@ -345,6 +345,7 @@ pub fn categorizeOperand(
345 .fpext,345 .fpext,
346 .fptrunc,346 .fptrunc,
347 .intcast,347 .intcast,
348 .intcast_safe,
348 .trunc,349 .trunc,
349 .optional_payload,350 .optional_payload,
350 .optional_payload_ptr,351 .optional_payload_ptr,
...@@ -977,6 +978,7 @@ fn analyzeInst(...@@ -977,6 +978,7 @@ fn analyzeInst(
977 .fpext,978 .fpext,
978 .fptrunc,979 .fptrunc,
979 .intcast,980 .intcast,
981 .intcast_safe,
980 .trunc,982 .trunc,
981 .optional_payload,983 .optional_payload,
982 .optional_payload_ptr,984 .optional_payload_ptr,
src/Liveness/Verify.zig+1
...@@ -81,6 +81,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -81,6 +81,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
81 .fpext,81 .fpext,
82 .fptrunc,82 .fptrunc,
83 .intcast,83 .intcast,
84 .intcast_safe,
84 .trunc,85 .trunc,
85 .optional_payload,86 .optional_payload,
86 .optional_payload_ptr,87 .optional_payload_ptr,
src/Sema.zig+43-20
...@@ -8798,11 +8798,12 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -8798,11 +8798,12 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
8798 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);8798 const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
8799 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@enumFromInt");8799 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@enumFromInt");
8800 const operand = try sema.resolveInst(extra.rhs);8800 const operand = try sema.resolveInst(extra.rhs);
8801 const operand_ty = sema.typeOf(operand);
88018802
8802 if (dest_ty.zigTypeTag(zcu) != .@"enum") {8803 if (dest_ty.zigTypeTag(zcu) != .@"enum") {
8803 return sema.fail(block, src, "expected enum, found '{}'", .{dest_ty.fmt(pt)});8804 return sema.fail(block, src, "expected enum, found '{}'", .{dest_ty.fmt(pt)});
8804 }8805 }
8805 _ = try sema.checkIntType(block, operand_src, sema.typeOf(operand));8806 _ = try sema.checkIntType(block, operand_src, operand_ty);
88068807
8807 if (try sema.resolveValue(operand)) |int_val| {8808 if (try sema.resolveValue(operand)) |int_val| {
8808 if (dest_ty.isNonexhaustiveEnum(zcu)) {8809 if (dest_ty.isNonexhaustiveEnum(zcu)) {
...@@ -8830,23 +8831,39 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -8830,23 +8831,39 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
8830 }8831 }
88318832
8832 if (try sema.typeHasOnePossibleValue(dest_ty)) |opv| {8833 if (try sema.typeHasOnePossibleValue(dest_ty)) |opv| {
8833 const result = Air.internedToRef(opv.toIntern());8834 if (block.wantSafety()) {
8834 // The operand is runtime-known but the result is comptime-known. In8835 // The operand is runtime-known but the result is comptime-known. In
8835 // this case we still need a safety check.8836 // this case we still need a safety check.
8836 // TODO add a safety check here. we can't use is_named_enum_value -8837 const expect_int_val = switch (zcu.intern_pool.indexToKey(opv.toIntern())) {
8837 // it needs to convert the enum back to int and make sure it equals the operand int.8838 .enum_tag => |enum_tag| enum_tag.int,
8838 return result;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());
8839 }8846 }
88408847
8841 try sema.requireRuntimeBlock(block, src, operand_src);8848 try sema.requireRuntimeBlock(block, src, operand_src);
8842 const result = try block.addTyOp(.intcast, dest_ty, operand);8849 if (block.wantSafety()) {
8843 if (block.wantSafety() and !dest_ty.isNonexhaustiveEnum(zcu) and8850 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
8844 zcu.backendSupportsFeature(.is_named_enum_value))8851 _ = try sema.preparePanicId(src, .invalid_enum_value);
8845 {8852 return block.addTyOp(.intcast_safe, dest_ty, operand);
8846 const ok = try block.addUnOp(.is_named_enum_value, result);8853 } else {
8847 try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);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 }
8848 }8865 }
8849 return result;8866 return block.addTyOp(.intcast, dest_ty, operand);
8850}8867}
88518868
8852/// Pointer in, pointer out.8869/// Pointer in, pointer out.
...@@ -10192,7 +10209,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air...@@ -10192,7 +10209,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
10192 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast");10209 const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast");
10193 const operand = try sema.resolveInst(extra.rhs);10210 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);
10196}10213}
1019710214
10198fn intCast(10215fn intCast(
...@@ -10204,6 +10221,7 @@ fn intCast(...@@ -10204,6 +10221,7 @@ fn intCast(
10204 operand: Air.Inst.Ref,10221 operand: Air.Inst.Ref,
10205 operand_src: LazySrcLoc,10222 operand_src: LazySrcLoc,
10206 runtime_safety: bool,10223 runtime_safety: bool,
10224 safety_panics_are_enum: bool,
10207) CompileError!Air.Inst.Ref {10225) CompileError!Air.Inst.Ref {
10208 const pt = sema.pt;10226 const pt = sema.pt;
10209 const zcu = pt.zcu;10227 const zcu = pt.zcu;
...@@ -10242,7 +10260,7 @@ fn intCast(...@@ -10242,7 +10260,7 @@ fn intCast(
10242 const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst);10260 const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst);
10243 break :ok is_in_range;10261 break :ok is_in_range;
10244 };10262 };
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);
10246 }10264 }
10247 }10265 }
1024810266
...@@ -10251,6 +10269,11 @@ fn intCast(...@@ -10251,6 +10269,11 @@ fn intCast(
1025110269
10252 try sema.requireRuntimeBlock(block, src, operand_src);10270 try sema.requireRuntimeBlock(block, src, operand_src);
10253 if (runtime_safety and block.wantSafety()) {10271 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 }
10254 const actual_info = operand_scalar_ty.intInfo(zcu);10277 const actual_info = operand_scalar_ty.intInfo(zcu);
10255 const wanted_info = dest_scalar_ty.intInfo(zcu);10278 const wanted_info = dest_scalar_ty.intInfo(zcu);
10256 const actual_bits = actual_info.bits;10279 const actual_bits = actual_info.bits;
...@@ -10305,7 +10328,7 @@ fn intCast(...@@ -10305,7 +10328,7 @@ fn intCast(
10305 break :ok is_in_range;10328 break :ok is_in_range;
10306 };10329 };
10307 // TODO negative_to_unsigned?10330 // 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);
10309 } else {10332 } else {
10310 const ok = if (is_vector) ok: {10333 const ok = if (is_vector) ok: {
10311 const is_in_range = try block.addCmpVector(operand, dest_max, .lte);10334 const is_in_range = try block.addCmpVector(operand, dest_max, .lte);
...@@ -10321,7 +10344,7 @@ fn intCast(...@@ -10321,7 +10344,7 @@ fn intCast(
10321 const is_in_range = try block.addBinOp(.cmp_lte, operand, dest_max);10344 const is_in_range = try block.addBinOp(.cmp_lte, operand, dest_max);
10322 break :ok is_in_range;10345 break :ok is_in_range;
10323 };10346 };
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);
10325 }10348 }
10326 } else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) {10349 } else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) {
10327 // no shrinkage, yes sign loss10350 // no shrinkage, yes sign loss
...@@ -10344,7 +10367,7 @@ fn intCast(...@@ -10344,7 +10367,7 @@ fn intCast(
10344 const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst);10367 const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst);
10345 break :ok is_in_range;10368 break :ok is_in_range;
10346 };10369 };
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);
10348 }10371 }
10349 }10372 }
10350 return block.addTyOp(.intcast, dest_ty, operand);10373 return block.addTyOp(.intcast, dest_ty, operand);
...@@ -14149,7 +14172,7 @@ fn zirShl(...@@ -14149,7 +14172,7 @@ fn zirShl(
14149 {14172 {
14150 const max_int = Air.internedToRef((try lhs_ty.maxInt(pt, lhs_ty)).toIntern());14173 const max_int = Air.internedToRef((try lhs_ty.maxInt(pt, lhs_ty)).toIntern());
14151 const rhs_limited = try sema.analyzeMinMax(block, rhs_src, .min, &.{ rhs, max_int }, &.{ rhs_src, rhs_src });14174 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);
14153 } else {14176 } else {
14154 break :rhs rhs;14177 break :rhs rhs;
14155 }14178 }
src/Zcu.zig+2-1
...@@ -3313,7 +3313,7 @@ pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void {...@@ -3313,7 +3313,7 @@ pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void {
33133313
3314pub const Feature = enum {3314pub const Feature = enum {
3315 /// When this feature is enabled, Sema will emit calls to3315 /// When this feature is enabled, Sema will emit calls to
3316 /// `std.builtin.Panic` functions for things like safety checks and3316 /// `std.builtin.panic` functions for things like safety checks and
3317 /// unreachables. Otherwise traps will be emitted.3317 /// unreachables. Otherwise traps will be emitted.
3318 panic_fn,3318 panic_fn,
3319 /// When this feature is enabled, Sema will insert tracer functions for gathering a stack3319 /// When this feature is enabled, Sema will insert tracer functions for gathering a stack
...@@ -3329,6 +3329,7 @@ pub const Feature = enum {...@@ -3329,6 +3329,7 @@ pub const Feature = enum {
3329 /// * `Air.Inst.Tag.add_safe`3329 /// * `Air.Inst.Tag.add_safe`
3330 /// * `Air.Inst.Tag.sub_safe`3330 /// * `Air.Inst.Tag.sub_safe`
3331 /// * `Air.Inst.Tag.mul_safe`3331 /// * `Air.Inst.Tag.mul_safe`
3332 /// * `Air.Inst.Tag.intcast_safe`
3332 /// The motivation for this feature is that it makes AIR smaller, and makes it easier3333 /// The motivation for this feature is that it makes AIR smaller, and makes it easier
3333 /// to generate better machine code in the backends. All backends should migrate to3334 /// to generate better machine code in the backends. All backends should migrate to
3334 /// enabling this feature.3335 /// enabling this feature.
src/arch/aarch64/CodeGen.zig+1
...@@ -871,6 +871,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -871,6 +871,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
871 .add_safe,871 .add_safe,
872 .sub_safe,872 .sub_safe,
873 .mul_safe,873 .mul_safe,
874 .intcast_safe,
874 => return self.fail("TODO implement safety_checked_instructions", .{}),875 => return self.fail("TODO implement safety_checked_instructions", .{}),
875876
876 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),877 .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 {...@@ -860,6 +860,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
860 .add_safe,860 .add_safe,
861 .sub_safe,861 .sub_safe,
862 .mul_safe,862 .mul_safe,
863 .intcast_safe,
863 => return self.fail("TODO implement safety_checked_instructions", .{}),864 => return self.fail("TODO implement safety_checked_instructions", .{}),
864865
865 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),866 .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 {...@@ -1517,6 +1517,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1517 .add_safe,1517 .add_safe,
1518 .sub_safe,1518 .sub_safe,
1519 .mul_safe,1519 .mul_safe,
1520 .intcast_safe,
1520 => return func.fail("TODO implement safety_checked_instructions", .{}),1521 => return func.fail("TODO implement safety_checked_instructions", .{}),
15211522
1522 .cmp_lt,1523 .cmp_lt,
src/arch/sparc64/CodeGen.zig+1
...@@ -714,6 +714,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -714,6 +714,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
714 .add_safe,714 .add_safe,
715 .sub_safe,715 .sub_safe,
716 .mul_safe,716 .mul_safe,
717 .intcast_safe,
717 => @panic("TODO implement safety_checked_instructions"),718 => @panic("TODO implement safety_checked_instructions"),
718719
719 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),720 .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 {...@@ -2084,6 +2084,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2084 .add_safe,2084 .add_safe,
2085 .sub_safe,2085 .sub_safe,
2086 .mul_safe,2086 .mul_safe,
2087 .intcast_safe,
2087 => return cg.fail("TODO implement safety_checked_instructions", .{}),2088 => return cg.fail("TODO implement safety_checked_instructions", .{}),
20882089
2089 .work_item_id,2090 .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 {...@@ -2549,6 +2549,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
2549 .add_safe,2549 .add_safe,
2550 .sub_safe,2550 .sub_safe,
2551 .mul_safe,2551 .mul_safe,
2552 .intcast_safe,
2552 => return cg.fail("TODO implement safety_checked_instructions", .{}),2553 => return cg.fail("TODO implement safety_checked_instructions", .{}),
25532554
2554 .add_optimized => try cg.airBinOp(inst, .add),2555 .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,...@@ -3436,6 +3436,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3436 .add_safe,3436 .add_safe,
3437 .sub_safe,3437 .sub_safe,
3438 .mul_safe,3438 .mul_safe,
3439 .intcast_safe,
3439 => return f.fail("TODO implement safety_checked_instructions", .{}),3440 => return f.fail("TODO implement safety_checked_instructions", .{}),
34403441
3441 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),3442 .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 {...@@ -5162,7 +5162,8 @@ pub const FuncGen = struct {
5162 .try_cold => try self.airTry(body[i..], true),5162 .try_cold => try self.airTry(body[i..], true),
5163 .try_ptr => try self.airTryPtr(inst, false),5163 .try_ptr => try self.airTryPtr(inst, false),
5164 .try_ptr_cold => try self.airTryPtr(inst, true),5164 .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),
5166 .trunc => try self.airTrunc(inst),5167 .trunc => try self.airTrunc(inst),
5167 .fptrunc => try self.airFptrunc(inst),5168 .fptrunc => try self.airFptrunc(inst),
5168 .fpext => try self.airFpext(inst),5169 .fpext => try self.airFpext(inst),
...@@ -9246,20 +9247,110 @@ pub const FuncGen = struct {...@@ -9246,20 +9247,110 @@ pub const FuncGen = struct {
9246 }9247 }
9247 }9248 }
92489249
9249 fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {9250 fn airIntCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) !Builder.Value {
9250 const o = self.ng.object;9251 const o = fg.ng.object;
9251 const zcu = o.pt.zcu;9252 const zcu = o.pt.zcu;
9252 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;9253 const ty_op = fg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
9253 const dest_ty = self.typeOfIndex(inst);9254 const dest_ty = fg.typeOfIndex(inst);
9254 const dest_llvm_ty = try o.lowerType(dest_ty);9255 const dest_llvm_ty = try o.lowerType(dest_ty);
9255 const operand = try self.resolveInst(ty_op.operand);9256 const operand = try fg.resolveInst(ty_op.operand);
9256 const operand_ty = self.typeOf(ty_op.operand);9257 const operand_ty = fg.typeOf(ty_op.operand);
9257 const operand_info = operand_ty.intInfo(zcu);9258 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) {
9260 .signed => .signed,9330 .signed => .signed,
9261 .unsigned => .unsigned,9331 .unsigned => .unsigned,
9262 }, operand, dest_llvm_ty, "");9332 }, 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;
9263 }9354 }
92649355
9265 fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {9356 fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
...@@ -12953,3 +13044,42 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {...@@ -12953,3 +13044,42 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
12953 => unreachable,13044 => unreachable,
12954 }13045 }
12955}13046}
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 {...@@ -223,6 +223,7 @@ const Writer = struct {
223 .fptrunc,223 .fptrunc,
224 .fpext,224 .fpext,
225 .intcast,225 .intcast,
226 .intcast_safe,
226 .trunc,227 .trunc,
227 .optional_payload,228 .optional_payload,
228 .optional_payload_ptr,229 .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