Skip to content

Commit b01d6b1

Browse files
committed
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
1 parent c5e34df commit b01d6b1

File tree

17 files changed

+249
-29
lines changed

17 files changed

+249
-29
lines changed

src/Air.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1019810215
fn 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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3313,7 +3313,7 @@ pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void {
33133313

33143314
pub 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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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", .{}),

0 commit comments

Comments
 (0)