| author | |
| committer | |
| log | b01d6b156cf4d273be40a6e6288f4766f71f4a29 |
| tree | 084bfcf9ad8dfbce7879f750383a3091f699f52f |
| parent | c5e34df555a98c51919eb45b0ab2a5ee3b37f4bf |
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: #2194617 files changed, 249 insertions(+), 29 deletions(-)
src/Air.zig+8| ... | ... | @@ -574,6 +574,12 @@ pub const Inst = struct { |
| 574 | 574 | /// See `trunc` for integer truncation. |
| 575 | 575 | /// Uses the `ty_op` field. |
| 576 | 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 | 583 | /// Truncate higher bits from an integer, resulting in an integer with the same |
| 578 | 584 | /// sign but an equal or smaller number of bits. |
| 579 | 585 | /// Uses the `ty_op` field. |
| ... | ... | @@ -1463,6 +1469,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) |
| 1463 | 1469 | .fpext, |
| 1464 | 1470 | .fptrunc, |
| 1465 | 1471 | .intcast, |
| 1472 | .intcast_safe, | |
| 1466 | 1473 | .trunc, |
| 1467 | 1474 | .optional_payload, |
| 1468 | 1475 | .optional_payload_ptr, |
| ... | ... | @@ -1712,6 +1719,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { |
| 1712 | 1719 | .add_safe, |
| 1713 | 1720 | .sub_safe, |
| 1714 | 1721 | .mul_safe, |
| 1722 | .intcast_safe, | |
| 1715 | 1723 | => true, |
| 1716 | 1724 | |
| 1717 | 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 | 104 | .fptrunc, |
| 105 | 105 | .fpext, |
| 106 | 106 | .intcast, |
| 107 | .intcast_safe, | |
| 107 | 108 | .trunc, |
| 108 | 109 | .optional_payload, |
| 109 | 110 | .optional_payload_ptr, |
src/Liveness.zig+2| ... | ... | @@ -345,6 +345,7 @@ pub fn categorizeOperand( |
| 345 | 345 | .fpext, |
| 346 | 346 | .fptrunc, |
| 347 | 347 | .intcast, |
| 348 | .intcast_safe, | |
| 348 | 349 | .trunc, |
| 349 | 350 | .optional_payload, |
| 350 | 351 | .optional_payload_ptr, |
| ... | ... | @@ -977,6 +978,7 @@ fn analyzeInst( |
| 977 | 978 | .fpext, |
| 978 | 979 | .fptrunc, |
| 979 | 980 | .intcast, |
| 981 | .intcast_safe, | |
| 980 | 982 | .trunc, |
| 981 | 983 | .optional_payload, |
| 982 | 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 | 81 | .fpext, |
| 82 | 82 | .fptrunc, |
| 83 | 83 | .intcast, |
| 84 | .intcast_safe, | |
| 84 | 85 | .trunc, |
| 85 | 86 | .optional_payload, |
| 86 | 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 | 8798 | const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); |
| 8799 | 8799 | const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@enumFromInt"); |
| 8800 | 8800 | const operand = try sema.resolveInst(extra.rhs); |
| 8801 | const operand_ty = sema.typeOf(operand); | |
| 8801 | 8802 | |
| 8802 | 8803 | if (dest_ty.zigTypeTag(zcu) != .@"enum") { |
| 8803 | 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); | |
| 8806 | 8807 | |
| 8807 | 8808 | if (try sema.resolveValue(operand)) |int_val| { |
| 8808 | 8809 | if (dest_ty.isNonexhaustiveEnum(zcu)) { |
| ... | ... | @@ -8830,23 +8831,39 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8830 | 8831 | } |
| 8831 | 8832 | |
| 8832 | 8833 | 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()); | |
| 8839 | 8846 | } |
| 8840 | 8847 | |
| 8841 | 8848 | 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 | } | |
| 8848 | 8865 | } |
| 8849 | return result; | |
| 8866 | return block.addTyOp(.intcast, dest_ty, operand); | |
| 8850 | 8867 | } |
| 8851 | 8868 | |
| 8852 | 8869 | /// Pointer in, pointer out. |
| ... | ... | @@ -10192,7 +10209,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air |
| 10192 | 10209 | const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast"); |
| 10193 | 10210 | const operand = try sema.resolveInst(extra.rhs); |
| 10194 | 10211 | |
| 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 | } |
| 10197 | 10214 | |
| 10198 | 10215 | fn intCast( |
| ... | ... | @@ -10204,6 +10221,7 @@ fn intCast( |
| 10204 | 10221 | operand: Air.Inst.Ref, |
| 10205 | 10222 | operand_src: LazySrcLoc, |
| 10206 | 10223 | runtime_safety: bool, |
| 10224 | safety_panics_are_enum: bool, | |
| 10207 | 10225 | ) CompileError!Air.Inst.Ref { |
| 10208 | 10226 | const pt = sema.pt; |
| 10209 | 10227 | const zcu = pt.zcu; |
| ... | ... | @@ -10242,7 +10260,7 @@ fn intCast( |
| 10242 | 10260 | const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst); |
| 10243 | 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 | } |
| 10248 | 10266 | |
| ... | ... | @@ -10251,6 +10269,11 @@ fn intCast( |
| 10251 | 10269 | |
| 10252 | 10270 | try sema.requireRuntimeBlock(block, src, operand_src); |
| 10253 | 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 | 10277 | const actual_info = operand_scalar_ty.intInfo(zcu); |
| 10255 | 10278 | const wanted_info = dest_scalar_ty.intInfo(zcu); |
| 10256 | 10279 | const actual_bits = actual_info.bits; |
| ... | ... | @@ -10305,7 +10328,7 @@ fn intCast( |
| 10305 | 10328 | break :ok is_in_range; |
| 10306 | 10329 | }; |
| 10307 | 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 | 10332 | } else { |
| 10310 | 10333 | const ok = if (is_vector) ok: { |
| 10311 | 10334 | const is_in_range = try block.addCmpVector(operand, dest_max, .lte); |
| ... | ... | @@ -10321,7 +10344,7 @@ fn intCast( |
| 10321 | 10344 | const is_in_range = try block.addBinOp(.cmp_lte, operand, dest_max); |
| 10322 | 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 | 10349 | } else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) { |
| 10327 | 10350 | // no shrinkage, yes sign loss |
| ... | ... | @@ -10344,7 +10367,7 @@ fn intCast( |
| 10344 | 10367 | const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst); |
| 10345 | 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 | 10373 | return block.addTyOp(.intcast, dest_ty, operand); |
| ... | ... | @@ -14149,7 +14172,7 @@ fn zirShl( |
| 14149 | 14172 | { |
| 14150 | 14173 | const max_int = Air.internedToRef((try lhs_ty.maxInt(pt, lhs_ty)).toIntern()); |
| 14151 | 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 | 14176 | } else { |
| 14154 | 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 | 3313 | |
| 3314 | 3314 | pub const Feature = enum { |
| 3315 | 3315 | /// 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 | |
| 3317 | 3317 | /// unreachables. Otherwise traps will be emitted. |
| 3318 | 3318 | panic_fn, |
| 3319 | 3319 | /// When this feature is enabled, Sema will insert tracer functions for gathering a stack |
| ... | ... | @@ -3329,6 +3329,7 @@ pub const Feature = enum { |
| 3329 | 3329 | /// * `Air.Inst.Tag.add_safe` |
| 3330 | 3330 | /// * `Air.Inst.Tag.sub_safe` |
| 3331 | 3331 | /// * `Air.Inst.Tag.mul_safe` |
| 3332 | /// * `Air.Inst.Tag.intcast_safe` | |
| 3332 | 3333 | /// The motivation for this feature is that it makes AIR smaller, and makes it easier |
| 3333 | 3334 | /// to generate better machine code in the backends. All backends should migrate to |
| 3334 | 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 | 871 | .add_safe, |
| 872 | 872 | .sub_safe, |
| 873 | 873 | .mul_safe, |
| 874 | .intcast_safe, | |
| 874 | 875 | => return self.fail("TODO implement safety_checked_instructions", .{}), |
| 875 | 876 | |
| 876 | 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 | 860 | .add_safe, |
| 861 | 861 | .sub_safe, |
| 862 | 862 | .mul_safe, |
| 863 | .intcast_safe, | |
| 863 | 864 | => return self.fail("TODO implement safety_checked_instructions", .{}), |
| 864 | 865 | |
| 865 | 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 | 1517 | .add_safe, |
| 1518 | 1518 | .sub_safe, |
| 1519 | 1519 | .mul_safe, |
| 1520 | .intcast_safe, | |
| 1520 | 1521 | => return func.fail("TODO implement safety_checked_instructions", .{}), |
| 1521 | 1522 | |
| 1522 | 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 | 714 | .add_safe, |
| 715 | 715 | .sub_safe, |
| 716 | 716 | .mul_safe, |
| 717 | .intcast_safe, | |
| 717 | 718 | => @panic("TODO implement safety_checked_instructions"), |
| 718 | 719 | |
| 719 | 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 | 2084 | .add_safe, |
| 2085 | 2085 | .sub_safe, |
| 2086 | 2086 | .mul_safe, |
| 2087 | .intcast_safe, | |
| 2087 | 2088 | => return cg.fail("TODO implement safety_checked_instructions", .{}), |
| 2088 | 2089 | |
| 2089 | 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 | 2549 | .add_safe, |
| 2550 | 2550 | .sub_safe, |
| 2551 | 2551 | .mul_safe, |
| 2552 | .intcast_safe, | |
| 2552 | 2553 | => return cg.fail("TODO implement safety_checked_instructions", .{}), |
| 2553 | 2554 | |
| 2554 | 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 | 3436 | .add_safe, |
| 3437 | 3437 | .sub_safe, |
| 3438 | 3438 | .mul_safe, |
| 3439 | .intcast_safe, | |
| 3439 | 3440 | => return f.fail("TODO implement safety_checked_instructions", .{}), |
| 3440 | 3441 | |
| 3441 | 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 | 5162 | .try_cold => try self.airTry(body[i..], true), |
| 5163 | 5163 | .try_ptr => try self.airTryPtr(inst, false), |
| 5164 | 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 | 5167 | .trunc => try self.airTrunc(inst), |
| 5167 | 5168 | .fptrunc => try self.airFptrunc(inst), |
| 5168 | 5169 | .fpext => try self.airFpext(inst), |
| ... | ... | @@ -9246,20 +9247,110 @@ pub const FuncGen = struct { |
| 9246 | 9247 | } |
| 9247 | 9248 | } |
| 9248 | 9249 | |
| 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; | |
| 9251 | 9252 | 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); | |
| 9254 | 9255 | 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); | |
| 9257 | 9258 | const operand_info = operand_ty.intInfo(zcu); |
| 9258 | 9259 | |
| 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 | 9330 | .signed => .signed, |
| 9261 | 9331 | .unsigned => .unsigned, |
| 9262 | 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 | } |
| 9264 | 9355 | |
| 9265 | 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 | 13044 | => unreachable, |
| 12954 | 13045 | } |
| 12955 | 13046 | } |
| 13047 | ||
| 13048 | fn 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 | ||
| 13063 | fn 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 | 223 | .fptrunc, |
| 224 | 224 | .fpext, |
| 225 | 225 | .intcast, |
| 226 | .intcast_safe, | |
| 226 | 227 | .trunc, |
| 227 | 228 | .optional_payload, |
| 228 | 229 | .optional_payload_ptr, |
test/cases/safety/@enumFromInt truncated bits - exhaustive.zig	 created+23| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | pub 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 | ||
| 10 | pub 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 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | pub 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 | ||
| 10 | pub 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 |