diff --git a/src/Air.zig b/src/Air.zig index 74f4c57424..561ab86405 100644 --- a/src/Air.zig +++ b/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, diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig index c0b0d9b76c..dafb3e5ed7 100644 --- a/src/Air/Legalize.zig +++ b/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"); diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig index 94efaf114c..94ed60fbf2 100644 --- a/src/Air/Liveness.zig +++ b/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, diff --git a/src/Air/Liveness/Verify.zig b/src/Air/Liveness/Verify.zig index 2fafc44ab7..85345ceb66 100644 --- a/src/Air/Liveness/Verify.zig +++ b/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, diff --git a/src/Air/print.zig b/src/Air/print.zig index 7f5f396cae..53efa72356 100644 --- a/src/Air/print.zig +++ b/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, diff --git a/src/Air/types_resolved.zig b/src/Air/types_resolved.zig index 1def679794..2bf1ef108f 100644 --- a/src/Air/types_resolved.zig +++ b/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, diff --git a/src/Sema.zig b/src/Sema.zig index 953cdbf2ac..8308a17e95 100644 --- a/src/Sema.zig +++ b/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 { diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 4aaf6bf85c..a8a60e7ce7 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/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", .{}), diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 09304bf1de..98c1420863 100644 --- a/src/arch/arm/CodeGen.zig +++ b/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", .{}), diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index c82061a5dd..1743f37e84 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/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, diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index b35f45dd64..6da576bafc 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/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"), diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 2936025a49..6719f22a73 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/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, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index c652d48f3e..49e61e7067 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/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}); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 0fe3f80919..110c5a3127 100644 --- a/src/codegen/c.zig +++ b/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", .{}), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 658764ba3c..b4950179c4 100644 --- a/src/codegen/llvm.zig +++ b/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), diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b9eb56dd23..5170fa7428 100644 --- a/src/codegen/spirv.zig +++ b/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, diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 9152efe7a7..3884cc734c 100644 --- a/test/behavior/cast.zig +++ b/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(); +} diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - i0 max.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - i0 max.zig new file mode 100644 index 0000000000..38ec595b45 --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - i0 min.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - i0 min.zig new file mode 100644 index 0000000000..97a651855b --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - signed max.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - signed max.zig new file mode 100644 index 0000000000..cc19ee84ff --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - signed min.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - signed min.zig new file mode 100644 index 0000000000..abc95e396a --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - u0 max.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - u0 max.zig new file mode 100644 index 0000000000..f488d0291f --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - u0 min.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - u0 min.zig new file mode 100644 index 0000000000..8d459e1a5c --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned max.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned max.zig new file mode 100644 index 0000000000..95122abc8c --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned min.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned min.zig new file mode 100644 index 0000000000..fbc7cf18ac --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - vector max.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - vector max.zig new file mode 100644 index 0000000000..35b4c91509 --- /dev/null +++ b/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 diff --git a/test/cases/safety/@intFromFloat cannot fit - boundary case - vector min.zig b/test/cases/safety/@intFromFloat cannot fit - boundary case - vector min.zig new file mode 100644 index 0000000000..94ad509772 --- /dev/null +++ b/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