Commit 6ffa285fc3
Changed files (27)
src
arch
aarch64
arm
riscv64
sparc64
wasm
x86_64
test
behavior
cases
safety
src/Air/Liveness/Verify.zig
@@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
.array_to_slice,
.int_from_float,
.int_from_float_optimized,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
.float_from_int,
.get_union_tag,
.clz,
src/Air/Legalize.zig
@@ -112,6 +112,8 @@ pub const Feature = enum {
scalarize_trunc,
scalarize_int_from_float,
scalarize_int_from_float_optimized,
+ scalarize_int_from_float_safe,
+ scalarize_int_from_float_optimized_safe,
scalarize_float_from_int,
scalarize_shuffle_one,
scalarize_shuffle_two,
@@ -126,6 +128,12 @@ pub const Feature = enum {
/// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
/// Not compatible with `scalarize_intcast_safe`.
expand_intcast_safe,
+ /// Replace `int_from_float_safe` with an explicit safety check which `call`s the panic function on failure.
+ /// Not compatible with `scalarize_int_from_float_safe`.
+ expand_int_from_float_safe,
+ /// Replace `int_from_float_optimized_safe` with an explicit safety check which `call`s the panic function on failure.
+ /// Not compatible with `scalarize_int_from_float_optimized_safe`.
+ expand_int_from_float_optimized_safe,
/// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
/// Not compatible with `scalarize_add_safe`.
expand_add_safe,
@@ -225,6 +233,8 @@ pub const Feature = enum {
.trunc => .scalarize_trunc,
.int_from_float => .scalarize_int_from_float,
.int_from_float_optimized => .scalarize_int_from_float_optimized,
+ .int_from_float_safe => .scalarize_int_from_float_safe,
+ .int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe,
.float_from_int => .scalarize_float_from_int,
.shuffle_one => .scalarize_shuffle_one,
.shuffle_two => .scalarize_shuffle_two,
@@ -439,6 +449,20 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
},
+ .int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) {
+ assert(!l.features.has(.scalarize_int_from_float_safe));
+ continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false));
+ } else if (l.features.has(.scalarize_int_from_float_safe)) {
+ const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
+ if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
+ },
+ .int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) {
+ assert(!l.features.has(.scalarize_int_from_float_optimized_safe));
+ continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true));
+ } else if (l.features.has(.scalarize_int_from_float_optimized_safe)) {
+ const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
+ if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
+ },
.block, .loop => {
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = l.extraData(Air.Block, ty_pl.payload);
@@ -2001,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
.payload = try l.addBlockBody(main_block.body()),
} };
}
+fn safeIntFromFloatBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimized: bool) Error!Air.Inst.Data {
+ const pt = l.pt;
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
+
+ const operand_ref = ty_op.operand;
+ const operand_ty = l.typeOf(operand_ref);
+ const dest_ty = ty_op.ty.toType();
+
+ const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
+ const dest_scalar_ty = dest_ty.scalarType(zcu);
+ const int_info = dest_scalar_ty.intInfo(zcu);
+
+ // We emit 9 instructions in the worst case.
+ var inst_buf: [9]Air.Inst.Index = undefined;
+ try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
+ var main_block: Block = .init(&inst_buf);
+
+ // This check is a bit annoying because of floating-point rounding and the fact that this
+ // builtin truncates. We'll use a bigint for our calculations, because we need to construct
+ // integers exceeding the bounds of the result integer type, and we need to convert it to a
+ // float with a specific rounding mode to avoid errors.
+ // Our bigint may exceed the twos complement limit by one, so add an extra limb.
+ const limbs = try gpa.alloc(
+ std.math.big.Limb,
+ std.math.big.int.calcTwosCompLimbCount(int_info.bits) + 1,
+ );
+ defer gpa.free(limbs);
+ var big: std.math.big.int.Mutable = .init(limbs, 0);
+
+ // Check if the operand is lower than `min_int` when truncated to an integer.
+ big.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
+ const below_min_inst: Air.Inst.Index = if (!big.positive or big.eqlZero()) bad: {
+ // `min_int <= 0`, so check for `x <= min_int - 1`.
+ big.addScalar(big.toConst(), -1);
+ // For `<=`, we must round the RHS down, so that this value is the first `x` which returns `true`.
+ const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .floor);
+ break :bad try main_block.addCmp(l, .lte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
+ .vector = is_vector,
+ .optimized = optimized,
+ });
+ } else {
+ // `min_int > 0`, which is currently impossible. It would become possible under #3806, in
+ // which case we must detect `x < min_int`.
+ unreachable;
+ };
+
+ // Check if the operand is greater than `max_int` when truncated to an integer.
+ big.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
+ const above_max_inst: Air.Inst.Index = if (big.positive or big.eqlZero()) bad: {
+ // `max_int >= 0`, so check for `x >= max_int + 1`.
+ big.addScalar(big.toConst(), 1);
+ // For `>=`, we must round the RHS up, so that this value is the first `x` which returns `true`.
+ const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .ceil);
+ break :bad try main_block.addCmp(l, .gte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
+ .vector = is_vector,
+ .optimized = optimized,
+ });
+ } else {
+ // `max_int < 0`, which is currently impossible. It would become possible under #3806, in
+ // which case we must detect `x > max_int`.
+ unreachable;
+ };
+
+ // Combine the conditions.
+ const out_of_bounds_inst: Air.Inst.Index = main_block.add(l, .{
+ .tag = .bool_or,
+ .data = .{ .bin_op = .{
+ .lhs = below_min_inst.toRef(),
+ .rhs = above_max_inst.toRef(),
+ } },
+ });
+ const scalar_out_of_bounds_inst: Air.Inst.Index = if (is_vector) main_block.add(l, .{
+ .tag = .reduce,
+ .data = .{ .reduce = .{
+ .operand = out_of_bounds_inst.toRef(),
+ .operation = .Or,
+ } },
+ }) else out_of_bounds_inst;
+
+ // Now emit the actual condbr. "true" will be safety panic. "false" will be "ok", meaning we do
+ // the `int_from_float` and `br` the result to `orig_inst`.
+ var condbr: CondBr = .init(l, scalar_out_of_bounds_inst.toRef(), &main_block, .{ .true = .cold });
+ condbr.then_block = .init(main_block.stealRemainingCapacity());
+ try condbr.then_block.addPanic(l, .integer_part_out_of_bounds);
+ condbr.else_block = .init(condbr.then_block.stealRemainingCapacity());
+ const cast_inst = condbr.else_block.add(l, .{
+ .tag = if (optimized) .int_from_float_optimized else .int_from_float,
+ .data = .{ .ty_op = .{
+ .ty = Air.internedToRef(dest_ty.toIntern()),
+ .operand = operand_ref,
+ } },
+ });
+ _ = condbr.else_block.add(l, .{
+ .tag = .br,
+ .data = .{ .br = .{
+ .block_inst = orig_inst,
+ .operand = cast_inst.toRef(),
+ } },
+ });
+ _ = condbr.else_block.stealRemainingCapacity(); // we might not have used it all
+ try condbr.finish(l);
+
+ return .{ .ty_pl = .{
+ .ty = Air.internedToRef(dest_ty.toIntern()),
+ .payload = try l.addBlockBody(main_block.body()),
+ } };
+}
fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
const pt = l.pt;
const zcu = pt.zcu;
@@ -2378,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
} };
}
+/// Given a `std.math.big.int.Const`, converts it to a `Value` which is a float of type `float_ty`
+/// representing the same numeric value. If the integer cannot be exactly represented, `round`
+/// decides whether the value should be rounded up or down. If `is_vector`, then `float_ty` is
+/// instead a vector of floats, and the result value is a vector containing the converted scalar
+/// repeated N times.
+fn floatFromBigIntVal(
+ pt: Zcu.PerThread,
+ is_vector: bool,
+ float_ty: Type,
+ x: std.math.big.int.Const,
+ round: std.math.big.int.Round,
+) Error!Value {
+ const zcu = pt.zcu;
+ const scalar_ty = switch (is_vector) {
+ true => float_ty.childType(zcu),
+ false => float_ty,
+ };
+ assert(scalar_ty.zigTypeTag(zcu) == .float);
+ const scalar_val: Value = switch (scalar_ty.floatBits(zcu.getTarget())) {
+ 16 => try pt.floatValue(scalar_ty, x.toFloat(f16, round)[0]),
+ 32 => try pt.floatValue(scalar_ty, x.toFloat(f32, round)[0]),
+ 64 => try pt.floatValue(scalar_ty, x.toFloat(f64, round)[0]),
+ 80 => try pt.floatValue(scalar_ty, x.toFloat(f80, round)[0]),
+ 128 => try pt.floatValue(scalar_ty, x.toFloat(f128, round)[0]),
+ else => unreachable,
+ };
+ if (is_vector) {
+ return .fromInterned(try pt.intern(.{ .aggregate = .{
+ .ty = float_ty.toIntern(),
+ .storage = .{ .repeated_elem = scalar_val.toIntern() },
+ } }));
+ } else {
+ return scalar_val;
+ }
+}
+
const Block = struct {
instructions: []Air.Inst.Index,
len: usize,
@@ -2735,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
const Legalize = @This();
const std = @import("std");
const Type = @import("../Type.zig");
+const Value = @import("../Value.zig");
const Zcu = @import("../Zcu.zig");
src/Air/Liveness.zig
@@ -374,6 +374,8 @@ pub fn categorizeOperand(
.array_to_slice,
.int_from_float,
.int_from_float_optimized,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
.float_from_int,
.get_union_tag,
.clz,
@@ -1015,6 +1017,8 @@ fn analyzeInst(
.array_to_slice,
.int_from_float,
.int_from_float_optimized,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
.float_from_int,
.get_union_tag,
.clz,
src/Air/print.zig
@@ -250,6 +250,8 @@ const Writer = struct {
.splat,
.int_from_float,
.int_from_float_optimized,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
.get_union_tag,
.clz,
.ctz,
src/Air/types_resolved.zig
@@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
.array_to_slice,
.int_from_float,
.int_from_float_optimized,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
.float_from_int,
.splat,
.error_set_has_value,
src/arch/aarch64/CodeGen.zig
@@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.sub_safe,
.mul_safe,
.intcast_safe,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
=> return self.fail("TODO implement safety_checked_instructions", .{}),
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/arm/CodeGen.zig
@@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.sub_safe,
.mul_safe,
.intcast_safe,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
=> return self.fail("TODO implement safety_checked_instructions", .{}),
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/riscv64/CodeGen.zig
@@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{
.expand_intcast_safe,
+ .expand_int_from_float_safe,
+ .expand_int_from_float_optimized_safe,
.expand_add_safe,
.expand_sub_safe,
.expand_mul_safe,
@@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.sub_safe,
.mul_safe,
.intcast_safe,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
=> return func.fail("TODO implement safety_checked_instructions", .{}),
.cmp_lt,
src/arch/sparc64/CodeGen.zig
@@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.sub_safe,
.mul_safe,
.intcast_safe,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
=> @panic("TODO implement safety_checked_instructions"),
.is_named_enum_value => @panic("TODO implement is_named_enum_value"),
src/arch/wasm/CodeGen.zig
@@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{
.expand_intcast_safe,
+ .expand_int_from_float_safe,
+ .expand_int_from_float_optimized_safe,
.expand_add_safe,
.expand_sub_safe,
.expand_mul_safe,
@@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.sub_safe,
.mul_safe,
.intcast_safe,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
=> return cg.fail("TODO implement safety_checked_instructions", .{}),
.work_item_id,
src/arch/x86_64/CodeGen.zig
@@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
.reduce_one_elem_to_bitcast = true,
.expand_intcast_safe = true,
+ .expand_int_from_float_safe = true,
+ .expand_int_from_float_optimized_safe = true,
.expand_add_safe = true,
.expand_sub_safe = true,
.expand_mul_safe = true,
@@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
};
try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);
},
+ .int_from_float_safe => unreachable,
+ .int_from_float_optimized_safe => unreachable,
.float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {
const ty_op = air_datas[@intFromEnum(inst)].ty_op;
var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});
src/codegen/c.zig
@@ -27,6 +27,8 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
inline false, true => |supports_legalize| &.init(.{
// we don't currently ask zig1 to use safe optimization modes
.expand_intcast_safe = supports_legalize,
+ .expand_int_from_float_safe = supports_legalize,
+ .expand_int_from_float_optimized_safe = supports_legalize,
.expand_add_safe = supports_legalize,
.expand_sub_safe = supports_legalize,
.expand_mul_safe = supports_legalize,
@@ -3578,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.sub_safe,
.mul_safe,
.intcast_safe,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
=> return f.fail("TODO implement safety_checked_instructions", .{}),
.is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
src/codegen/llvm.zig
@@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
const Error = error{ OutOfMemory, CodegenFail };
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
- return null;
+ return comptime &.initMany(&.{
+ .expand_int_from_float_safe,
+ .expand_int_from_float_optimized_safe,
+ });
}
fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
@@ -4987,6 +4990,8 @@ pub const FuncGen = struct {
.int_from_float => try self.airIntFromFloat(inst, .normal),
.int_from_float_optimized => try self.airIntFromFloat(inst, .fast),
+ .int_from_float_safe => unreachable, // handled by `legalizeFeatures`
+ .int_from_float_optimized_safe => unreachable, // handled by `legalizeFeatures`
.array_to_slice => try self.airArrayToSlice(inst),
.float_from_int => try self.airFloatFromInt(inst),
src/codegen/spirv.zig
@@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{
.expand_intcast_safe,
+ .expand_int_from_float_safe,
+ .expand_int_from_float_optimized_safe,
.expand_add_safe,
.expand_sub_safe,
.expand_mul_safe,
src/Air.zig
@@ -683,6 +683,10 @@ pub const Inst = struct {
int_from_float,
/// Same as `int_from_float` with optimized float mode.
int_from_float_optimized,
+ /// Same as `int_from_float`, but with a safety check that the operand is in bounds.
+ int_from_float_safe,
+ /// Same as `int_from_float_optimized`, but with a safety check that the operand is in bounds.
+ int_from_float_optimized_safe,
/// Given an integer operand, return the float with the closest mathematical meaning.
/// Uses the `ty_op` field.
float_from_int,
@@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.array_to_slice,
.int_from_float,
.int_from_float_optimized,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
.float_from_int,
.splat,
.get_union_tag,
@@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.sub_safe,
.mul_safe,
.intcast_safe,
+ .int_from_float_safe,
+ .int_from_float_optimized_safe,
=> true,
.add,
src/Sema.zig
@@ -22178,44 +22178,34 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
try sema.requireRuntimeBlock(block, src, operand_src);
if (dest_scalar_ty.intInfo(zcu).bits == 0) {
- if (!is_vector) {
- if (block.wantSafety()) {
- const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, operand, Air.internedToRef((try pt.floatValue(operand_ty, 0.0)).toIntern()));
- try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
- }
- return Air.internedToRef((try pt.intValue(dest_ty, 0)).toIntern());
- }
if (block.wantSafety()) {
- const len = dest_ty.vectorLen(zcu);
- for (0..len) |i| {
- const idx_ref = try pt.intRef(.usize, i);
- const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref);
- const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, elem_ref, Air.internedToRef((try pt.floatValue(operand_scalar_ty, 0.0)).toIntern()));
- try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
- }
- }
+ // Emit an explicit safety check. We can do this one like `abs(x) < 1`.
+ const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
+ const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
+ const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
+ const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
+ try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
+ }
+ const scalar_val = try pt.intValue(dest_scalar_ty, 0);
+ if (!is_vector) return Air.internedToRef(scalar_val.toIntern());
return Air.internedToRef(try pt.intern(.{ .aggregate = .{
.ty = dest_ty.toIntern(),
- .storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() },
+ .storage = .{ .repeated_elem = scalar_val.toIntern() },
} }));
}
- const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand);
if (block.wantSafety()) {
- const back = try block.addTyOp(.float_from_int, operand_ty, result);
- const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back);
- const ok = if (is_vector) ok: {
- const ok_pos = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, 1.0))).toIntern()), .lt);
- const ok_neg = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, -1.0))).toIntern()), .gt);
- const ok = try block.addBinOp(.bit_and, ok_pos, ok_neg);
- break :ok try block.addReduce(ok, .And);
- } else ok: {
- const ok_pos = try block.addBinOp(if (block.float_mode == .optimized) .cmp_lt_optimized else .cmp_lt, diff, Air.internedToRef((try pt.floatValue(operand_ty, 1.0)).toIntern()));
- const ok_neg = try block.addBinOp(if (block.float_mode == .optimized) .cmp_gt_optimized else .cmp_gt, diff, Air.internedToRef((try pt.floatValue(operand_ty, -1.0)).toIntern()));
- break :ok try block.addBinOp(.bool_and, ok_pos, ok_neg);
- };
- try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
+ if (zcu.backendSupportsFeature(.panic_fn)) {
+ _ = try sema.preparePanicId(src, .integer_part_out_of_bounds);
+ }
+ return block.addTyOp(switch (block.float_mode) {
+ .optimized => .int_from_float_optimized_safe,
+ .strict => .int_from_float_safe,
+ }, dest_ty, operand);
}
- return result;
+ return block.addTyOp(switch (block.float_mode) {
+ .optimized => .int_from_float_optimized,
+ .strict => .int_from_float,
+ }, dest_ty, operand);
}
fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
test/behavior/cast.zig
@@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" {
test "@floatFromInt" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+ if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct {
@@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {
try expect(result[0][0] == 10);
try expect(result[0][1] == 20);
}
+
+test "@intFromFloat boundary cases" {
+ if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
+
+ const S = struct {
+ fn case(comptime I: type, x: f32, bump: enum { up, down }, expected: I) !void {
+ const input: f32 = switch (bump) {
+ .up => std.math.nextAfter(f32, x, std.math.inf(f32)),
+ .down => std.math.nextAfter(f32, x, -std.math.inf(f32)),
+ };
+ const output: I = @intFromFloat(input);
+ try expect(output == expected);
+ }
+ fn doTheTest() !void {
+ try case(u8, 256.0, .down, 255);
+ try case(u8, -1.0, .up, 0);
+ try case(i8, 128.0, .down, 127);
+ try case(i8, -129.0, .up, -128);
+
+ try case(u0, 1.0, .down, 0);
+ try case(u0, -1.0, .up, 0);
+ try case(i0, 1.0, .down, 0);
+ try case(i0, -1.0, .up, 0);
+
+ try case(u10, 1024.0, .down, 1023);
+ try case(u10, -1.0, .up, 0);
+ try case(i10, 512.0, .down, 511);
+ try case(i10, -513.0, .up, -512);
+ }
+ };
+ try S.doTheTest();
+ try comptime S.doTheTest();
+}
+
+test "@intFromFloat vector boundary cases" {
+ if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
+
+ const S = struct {
+ fn case(comptime I: type, unshifted_inputs: [2]f32, expected: [2]I) !void {
+ const inputs: @Vector(2, f32) = .{
+ std.math.nextAfter(f32, unshifted_inputs[0], std.math.inf(f32)),
+ std.math.nextAfter(f32, unshifted_inputs[1], -std.math.inf(f32)),
+ };
+ const outputs: @Vector(2, I) = @intFromFloat(inputs);
+ try expect(outputs[0] == expected[0]);
+ try expect(outputs[1] == expected[1]);
+ }
+ fn doTheTest() !void {
+ try case(u8, .{ -1.0, 256.0 }, .{ 0, 255 });
+ try case(i8, .{ -129.0, 128.0 }, .{ -128, 127 });
+
+ try case(u0, .{ -1.0, 1.0 }, .{ 0, 0 });
+ try case(i0, .{ -1.0, 1.0 }, .{ 0, 0 });
+
+ try case(u10, .{ -1.0, 1024.0 }, .{ 0, 1023 });
+ try case(i10, .{ -513.0, 512.0 }, .{ -512, 511 });
+ }
+ };
+ try S.doTheTest();
+ try comptime S.doTheTest();
+}
test/cases/safety/@intFromFloat cannot fit - boundary case - i0 max.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = 1.0;
+pub fn main() !void {
+ _ = @as(i0, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - i0 min.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = -1.0;
+pub fn main() !void {
+ _ = @as(i0, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - signed max.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = 128;
+pub fn main() !void {
+ _ = @as(i8, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - signed min.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = -129;
+pub fn main() !void {
+ _ = @as(i8, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - u0 max.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = 1.0;
+pub fn main() !void {
+ _ = @as(u0, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - u0 min.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = -1.0;
+pub fn main() !void {
+ _ = @as(u0, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned max.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = 256;
+pub fn main() !void {
+ _ = @as(u8, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned min.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: f32 = -1;
+pub fn main() !void {
+ _ = @as(u8, @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - vector max.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: @Vector(2, f32) = .{ 100, 512 };
+pub fn main() !void {
+ _ = @as(@Vector(2, i10), @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - vector min.zig
@@ -0,0 +1,16 @@
+const std = @import("std");
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+var x: @Vector(2, f32) = .{ 100, -513 };
+pub fn main() !void {
+ _ = @as(@Vector(2, i10), @intFromFloat(x));
+ return error.TestFailed;
+}
+// run
+// backend=stage2,llvm
+// target=native