Skip to content

Commit 4c4dacf

Browse files
committed
Legalize: replace safety_checked_instructions
This adds 4 `Legalize.Feature`s: * `expand_intcast_safe` * `expand_add_safe` * `expand_sub_safe` * `expand_mul_safe` These do pretty much what they say on the tin. This logic was previously in Sema, used when `Zcu.Feature.safety_checked_instructions` was not supported by the backend. That `Zcu.Feature` has been removed in favour of this legalization.
1 parent 77e6513 commit 4c4dacf

File tree

10 files changed

+558
-244
lines changed

10 files changed

+558
-244
lines changed

src/Air.zig

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ pub const Inst = struct {
5050
/// is the same as both operands.
5151
/// The panic handler function must be populated before lowering AIR
5252
/// that contains this instruction.
53-
/// This instruction will only be emitted if the backend has the
54-
/// feature `safety_checked_instructions`.
5553
/// Uses the `bin_op` field.
5654
add_safe,
5755
/// Float addition. The instruction is allowed to have equal or more
@@ -79,8 +77,6 @@ pub const Inst = struct {
7977
/// is the same as both operands.
8078
/// The panic handler function must be populated before lowering AIR
8179
/// that contains this instruction.
82-
/// This instruction will only be emitted if the backend has the
83-
/// feature `safety_checked_instructions`.
8480
/// Uses the `bin_op` field.
8581
sub_safe,
8682
/// Float subtraction. The instruction is allowed to have equal or more
@@ -108,8 +104,6 @@ pub const Inst = struct {
108104
/// is the same as both operands.
109105
/// The panic handler function must be populated before lowering AIR
110106
/// that contains this instruction.
111-
/// This instruction will only be emitted if the backend has the
112-
/// feature `safety_checked_instructions`.
113107
/// Uses the `bin_op` field.
114108
mul_safe,
115109
/// Float multiplication. The instruction is allowed to have equal or more

src/Air/Legalize.zig

Lines changed: 512 additions & 74 deletions
Large diffs are not rendered by default.

src/Sema.zig

Lines changed: 9 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -8912,21 +8912,10 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
89128912

89138913
try sema.requireRuntimeBlock(block, src, operand_src);
89148914
if (block.wantSafety()) {
8915-
if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
8915+
if (zcu.backendSupportsFeature(.panic_fn)) {
89168916
_ = try sema.preparePanicId(src, .invalid_enum_value);
8917-
return block.addTyOp(.intcast_safe, dest_ty, operand);
8918-
} else {
8919-
// Slightly silly fallback case...
8920-
const int_tag_ty = dest_ty.intTagType(zcu);
8921-
// Use `intCast`, since it'll set up the Sema-emitted safety checks for us!
8922-
const int_val = try sema.intCast(block, src, int_tag_ty, src, operand, src, true, true);
8923-
const result = try block.addBitCast(dest_ty, int_val);
8924-
if (!dest_ty.isNonexhaustiveEnum(zcu) and zcu.backendSupportsFeature(.is_named_enum_value)) {
8925-
const ok = try block.addUnOp(.is_named_enum_value, result);
8926-
try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
8927-
}
8928-
return result;
89298917
}
8918+
return block.addTyOp(.intcast_safe, dest_ty, operand);
89308919
}
89318920
return block.addTyOp(.intcast, dest_ty, operand);
89328921
}
@@ -10331,90 +10320,11 @@ fn intCast(
1033110320

1033210321
try sema.requireRuntimeBlock(block, src, operand_src);
1033310322
if (runtime_safety and block.wantSafety()) {
10334-
if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
10323+
if (zcu.backendSupportsFeature(.panic_fn)) {
1033510324
_ = try sema.preparePanicId(src, .negative_to_unsigned);
1033610325
_ = try sema.preparePanicId(src, .cast_truncated_data);
10337-
return block.addTyOp(.intcast_safe, dest_ty, operand);
10338-
}
10339-
const actual_info = operand_scalar_ty.intInfo(zcu);
10340-
const wanted_info = dest_scalar_ty.intInfo(zcu);
10341-
const actual_bits = actual_info.bits;
10342-
const wanted_bits = wanted_info.bits;
10343-
const actual_value_bits = actual_bits - @intFromBool(actual_info.signedness == .signed);
10344-
const wanted_value_bits = wanted_bits - @intFromBool(wanted_info.signedness == .signed);
10345-
10346-
// range shrinkage
10347-
// requirement: int value fits into target type
10348-
if (wanted_value_bits < actual_value_bits) {
10349-
const dest_max_val_scalar = try dest_scalar_ty.maxIntScalar(pt, operand_scalar_ty);
10350-
const dest_max_val = try sema.splat(operand_ty, dest_max_val_scalar);
10351-
const dest_max = Air.internedToRef(dest_max_val.toIntern());
10352-
10353-
if (actual_info.signedness == .signed) {
10354-
const diff = try block.addBinOp(.sub_wrap, dest_max, operand);
10355-
10356-
// Reinterpret the sign-bit as part of the value. This will make
10357-
// negative differences (`operand` > `dest_max`) appear too big.
10358-
const unsigned_scalar_operand_ty = try pt.intType(.unsigned, actual_bits);
10359-
const unsigned_operand_ty = if (is_vector) try pt.vectorType(.{
10360-
.len = dest_ty.vectorLen(zcu),
10361-
.child = unsigned_scalar_operand_ty.toIntern(),
10362-
}) else unsigned_scalar_operand_ty;
10363-
const diff_unsigned = try block.addBitCast(unsigned_operand_ty, diff);
10364-
10365-
// If the destination type is signed, then we need to double its
10366-
// range to account for negative values.
10367-
const dest_range_val = if (wanted_info.signedness == .signed) range_val: {
10368-
const one_scalar = try pt.intValue(unsigned_scalar_operand_ty, 1);
10369-
const one = if (is_vector) Value.fromInterned(try pt.intern(.{ .aggregate = .{
10370-
.ty = unsigned_operand_ty.toIntern(),
10371-
.storage = .{ .repeated_elem = one_scalar.toIntern() },
10372-
} })) else one_scalar;
10373-
const range_minus_one = try dest_max_val.shl(one, unsigned_operand_ty, sema.arena, pt);
10374-
const result = try arith.addWithOverflow(sema, unsigned_operand_ty, range_minus_one, one);
10375-
assert(result.overflow_bit.compareAllWithZero(.eq, zcu));
10376-
break :range_val result.wrapped_result;
10377-
} else try pt.getCoerced(dest_max_val, unsigned_operand_ty);
10378-
const dest_range = Air.internedToRef(dest_range_val.toIntern());
10379-
10380-
const ok = if (is_vector) ok: {
10381-
const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte);
10382-
const all_in_range = try block.addReduce(is_in_range, .And);
10383-
break :ok all_in_range;
10384-
} else ok: {
10385-
const is_in_range = try block.addBinOp(.cmp_lte, diff_unsigned, dest_range);
10386-
break :ok is_in_range;
10387-
};
10388-
// TODO negative_to_unsigned?
10389-
try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
10390-
} else {
10391-
const ok = if (is_vector) ok: {
10392-
const is_in_range = try block.addCmpVector(operand, dest_max, .lte);
10393-
const all_in_range = try block.addReduce(is_in_range, .And);
10394-
break :ok all_in_range;
10395-
} else ok: {
10396-
const is_in_range = try block.addBinOp(.cmp_lte, operand, dest_max);
10397-
break :ok is_in_range;
10398-
};
10399-
try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
10400-
}
10401-
} else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) {
10402-
// no shrinkage, yes sign loss
10403-
// requirement: signed to unsigned >= 0
10404-
const ok = if (is_vector) ok: {
10405-
const scalar_zero = try pt.intValue(operand_scalar_ty, 0);
10406-
const zero_val = try sema.splat(operand_ty, scalar_zero);
10407-
const zero_inst = Air.internedToRef(zero_val.toIntern());
10408-
const is_in_range = try block.addCmpVector(operand, zero_inst, .gte);
10409-
const all_in_range = try block.addReduce(is_in_range, .And);
10410-
break :ok all_in_range;
10411-
} else ok: {
10412-
const zero_inst = Air.internedToRef((try pt.intValue(operand_ty, 0)).toIntern());
10413-
const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst);
10414-
break :ok is_in_range;
10415-
};
10416-
try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .negative_to_unsigned);
1041710326
}
10327+
return block.addTyOp(.intcast_safe, dest_ty, operand);
1041810328
}
1041910329
return block.addTyOp(.intcast, dest_ty, operand);
1042010330
}
@@ -14316,7 +14226,7 @@ fn zirShl(
1431614226
}
1431714227

1431814228
if (air_tag == .shl_exact) {
14319-
const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty);
14229+
const op_ov_tuple_ty = try pt.overflowArithmeticTupleType(lhs_ty);
1432014230
const op_ov = try block.addInst(.{
1432114231
.tag = .shl_with_overflow,
1432214232
.data = .{ .ty_pl = .{
@@ -16111,7 +16021,7 @@ fn zirOverflowArithmetic(
1611116021
const maybe_lhs_val = try sema.resolveValue(lhs);
1611216022
const maybe_rhs_val = try sema.resolveValue(rhs);
1611316023

16114-
const tuple_ty = try sema.overflowArithmeticTupleType(dest_ty);
16024+
const tuple_ty = try pt.overflowArithmeticTupleType(dest_ty);
1611516025
const overflow_ty: Type = .fromInterned(ip.indexToKey(tuple_ty.toIntern()).tuple_type.types.get(ip)[1]);
1611616026

1611716027
var result: struct {
@@ -16284,24 +16194,6 @@ fn splat(sema: *Sema, ty: Type, val: Value) !Value {
1628416194
return Value.fromInterned(repeated);
1628516195
}
1628616196

16287-
fn overflowArithmeticTupleType(sema: *Sema, ty: Type) !Type {
16288-
const pt = sema.pt;
16289-
const zcu = pt.zcu;
16290-
const ip = &zcu.intern_pool;
16291-
const ov_ty: Type = if (ty.zigTypeTag(zcu) == .vector) try pt.vectorType(.{
16292-
.len = ty.vectorLen(zcu),
16293-
.child = .u1_type,
16294-
}) else .u1;
16295-
16296-
const types = [2]InternPool.Index{ ty.toIntern(), ov_ty.toIntern() };
16297-
const values = [2]InternPool.Index{ .none, .none };
16298-
const tuple_ty = try ip.getTupleType(zcu.gpa, pt.tid, .{
16299-
.types = &types,
16300-
.values = &values,
16301-
});
16302-
return .fromInterned(tuple_ty);
16303-
}
16304-
1630516197
fn analyzeArithmetic(
1630616198
sema: *Sema,
1630716199
block: *Block,
@@ -16477,41 +16369,10 @@ fn analyzeArithmetic(
1647716369
}
1647816370

1647916371
if (block.wantSafety() and want_safety and scalar_tag == .int) {
16480-
if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
16481-
if (air_tag != air_tag_safe) {
16482-
_ = try sema.preparePanicId(src, .integer_overflow);
16483-
}
16484-
return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
16485-
} else {
16486-
const maybe_op_ov: ?Air.Inst.Tag = switch (air_tag) {
16487-
.add => .add_with_overflow,
16488-
.sub => .sub_with_overflow,
16489-
.mul => .mul_with_overflow,
16490-
else => null,
16491-
};
16492-
if (maybe_op_ov) |op_ov_tag| {
16493-
const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(resolved_type);
16494-
const op_ov = try block.addInst(.{
16495-
.tag = op_ov_tag,
16496-
.data = .{ .ty_pl = .{
16497-
.ty = Air.internedToRef(op_ov_tuple_ty.toIntern()),
16498-
.payload = try sema.addExtra(Air.Bin{
16499-
.lhs = casted_lhs,
16500-
.rhs = casted_rhs,
16501-
}),
16502-
} },
16503-
});
16504-
const ov_bit = try sema.tupleFieldValByIndex(block, op_ov, 1, op_ov_tuple_ty);
16505-
const any_ov_bit = if (resolved_type.zigTypeTag(zcu) == .vector)
16506-
try block.addReduce(ov_bit, .Or)
16507-
else
16508-
ov_bit;
16509-
const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, .zero_u1);
16510-
16511-
try sema.addSafetyCheck(block, src, no_ov, .integer_overflow);
16512-
return sema.tupleFieldValByIndex(block, op_ov, 0, op_ov_tuple_ty);
16513-
}
16372+
if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) {
16373+
_ = try sema.preparePanicId(src, .integer_overflow);
1651416374
}
16375+
return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
1651516376
}
1651616377
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
1651716378
}

src/Zcu.zig

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3829,15 +3829,6 @@ pub const Feature = enum {
38293829
is_named_enum_value,
38303830
error_set_has_value,
38313831
field_reordering,
3832-
/// When this feature is supported, the backend supports the following AIR instructions:
3833-
/// * `Air.Inst.Tag.add_safe`
3834-
/// * `Air.Inst.Tag.sub_safe`
3835-
/// * `Air.Inst.Tag.mul_safe`
3836-
/// * `Air.Inst.Tag.intcast_safe`
3837-
/// The motivation for this feature is that it makes AIR smaller, and makes it easier
3838-
/// to generate better machine code in the backends. All backends should migrate to
3839-
/// enabling this feature.
3840-
safety_checked_instructions,
38413832
/// If the backend supports running from another thread.
38423833
separate_thread,
38433834
};

src/Zcu/PerThread.zig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3844,6 +3844,21 @@ pub fn nullValue(pt: Zcu.PerThread, opt_ty: Type) Allocator.Error!Value {
38443844
} }));
38453845
}
38463846

3847+
/// `ty` is an integer or a vector of integers.
3848+
pub fn overflowArithmeticTupleType(pt: Zcu.PerThread, ty: Type) !Type {
3849+
const zcu = pt.zcu;
3850+
const ip = &zcu.intern_pool;
3851+
const ov_ty: Type = if (ty.zigTypeTag(zcu) == .vector) try pt.vectorType(.{
3852+
.len = ty.vectorLen(zcu),
3853+
.child = .u1_type,
3854+
}) else .u1;
3855+
const tuple_ty = try ip.getTupleType(zcu.gpa, pt.tid, .{
3856+
.types = &.{ ty.toIntern(), ov_ty.toIntern() },
3857+
.values = &.{ .none, .none },
3858+
});
3859+
return .fromInterned(tuple_ty);
3860+
}
3861+
38473862
pub fn smallestUnsignedInt(pt: Zcu.PerThread, max: u64) Allocator.Error!Type {
38483863
return pt.intType(.unsigned, Type.smallestUnsignedBits(max));
38493864
}

src/arch/riscv64/CodeGen.zig

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ const Instruction = encoding.Instruction;
5252
const InnerError = CodeGenError || error{OutOfRegisters};
5353

5454
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
55-
return null;
55+
return comptime &.initMany(&.{
56+
.expand_intcast_safe,
57+
.expand_add_safe,
58+
.expand_sub_safe,
59+
.expand_mul_safe,
60+
});
5661
}
5762

5863
pt: Zcu.PerThread,

src/arch/wasm/CodeGen.zig

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ const compilerRtFloatAbbrev = target_util.compilerRtFloatAbbrev;
3232
const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
3333

3434
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
35-
return null;
35+
return comptime &.initMany(&.{
36+
.expand_intcast_safe,
37+
.expand_add_safe,
38+
.expand_sub_safe,
39+
.expand_mul_safe,
40+
});
3641
}
3742

3843
/// Reference to the function declaration the code

src/arch/x86_64/CodeGen.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
8888

8989
.unsplat_shift_rhs = false,
9090
.reduce_one_elem_to_bitcast = true,
91+
.expand_intcast_safe = true,
92+
.expand_add_safe = true,
93+
.expand_sub_safe = true,
94+
.expand_mul_safe = true,
9195
}),
9296
};
9397
}

src/codegen/spirv.zig

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ const SpvAssembler = @import("spirv/Assembler.zig");
2929
const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
3030

3131
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
32-
return null;
32+
return comptime &.initMany(&.{
33+
.expand_intcast_safe,
34+
.expand_add_safe,
35+
.expand_sub_safe,
36+
.expand_mul_safe,
37+
});
3338
}
3439

3540
pub const zig_call_abi_ver = 3;

src/target.zig

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -842,10 +842,6 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt
842842
.stage2_c, .stage2_llvm, .stage2_x86_64 => true,
843843
else => false,
844844
},
845-
.safety_checked_instructions => switch (backend) {
846-
.stage2_llvm => true,
847-
else => false,
848-
},
849845
.separate_thread => switch (backend) {
850846
.stage2_llvm => false,
851847
else => true,

0 commit comments

Comments
 (0)