authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-15 14:18:37+01:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-06-15 14:15:18-04:00
log6ffa285fc35651cb7f4b738a0f6e84718b821fd8
tree5c1307e2cfc9d6becd2bbcbdcd64979ca5dab5d9
parent6b41beb370ce62b4381396e0543e6eb7f70b18cf

compiler: fix `@intFromFloat` safety check

This safety check was completely broken; it triggered unchecked illegal behavior *in order to implement the safety check*. You definitely can't do that! Instead, we must explicitly check the boundaries. This is a tiny bit fiddly, because we need to make sure we do floating-point rounding in the correct direction, and also handle the fact that the operation truncates so the boundary works differently for min vs max. Instead of implementing this safety check in Sema, there are now dedicated AIR instructions for safety-checked intfromfloat (two instructions; which one is used depends on the float mode). Currently, no backend directly implements them; instead, a `Legalize.Feature` is added which expands the safety check, and this feature is enabled for all backends we currently test, including the LLVM backend. The `u0` case is still handled in Sema, because Sema needs to check for that anyway due to the comptime-known result. The old safety check here was also completely broken and has therefore been rewritten. In that case, we just check for 'abs(input) < 1.0'. I've added a bunch of test coverage for the boundary cases of `@intFromFloat`, both for successes (in `test/behavior/cast.zig`) and failures (in `test/cases/safety/`). Resolves: #24161

27 files changed, 463 insertions(+), 32 deletions(-)

src/Air.zig+8
...@@ -683,6 +683,10 @@ pub const Inst = struct {...@@ -683,6 +683,10 @@ pub const Inst = struct {
683 int_from_float,683 int_from_float,
684 /// Same as `int_from_float` with optimized float mode.684 /// Same as `int_from_float` with optimized float mode.
685 int_from_float_optimized,685 int_from_float_optimized,
686 /// Same as `int_from_float`, but with a safety check that the operand is in bounds.
687 int_from_float_safe,
688 /// Same as `int_from_float_optimized`, but with a safety check that the operand is in bounds.
689 int_from_float_optimized_safe,
686 /// Given an integer operand, return the float with the closest mathematical meaning.690 /// Given an integer operand, return the float with the closest mathematical meaning.
687 /// Uses the `ty_op` field.691 /// Uses the `ty_op` field.
688 float_from_int,692 float_from_int,
...@@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)...@@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
1612 .array_to_slice,1616 .array_to_slice,
1613 .int_from_float,1617 .int_from_float,
1614 .int_from_float_optimized,1618 .int_from_float_optimized,
1619 .int_from_float_safe,
1620 .int_from_float_optimized_safe,
1615 .float_from_int,1621 .float_from_int,
1616 .splat,1622 .splat,
1617 .get_union_tag,1623 .get_union_tag,
...@@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1842 .sub_safe,1848 .sub_safe,
1843 .mul_safe,1849 .mul_safe,
1844 .intcast_safe,1850 .intcast_safe,
1851 .int_from_float_safe,
1852 .int_from_float_optimized_safe,
1845 => true,1853 => true,
18461854
1847 .add,1855 .add,
src/Air/Legalize.zig+170
...@@ -112,6 +112,8 @@ pub const Feature = enum {...@@ -112,6 +112,8 @@ pub const Feature = enum {
112 scalarize_trunc,112 scalarize_trunc,
113 scalarize_int_from_float,113 scalarize_int_from_float,
114 scalarize_int_from_float_optimized,114 scalarize_int_from_float_optimized,
115 scalarize_int_from_float_safe,
116 scalarize_int_from_float_optimized_safe,
115 scalarize_float_from_int,117 scalarize_float_from_int,
116 scalarize_shuffle_one,118 scalarize_shuffle_one,
117 scalarize_shuffle_two,119 scalarize_shuffle_two,
...@@ -126,6 +128,12 @@ pub const Feature = enum {...@@ -126,6 +128,12 @@ pub const Feature = enum {
126 /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.128 /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
127 /// Not compatible with `scalarize_intcast_safe`.129 /// Not compatible with `scalarize_intcast_safe`.
128 expand_intcast_safe,130 expand_intcast_safe,
131 /// Replace `int_from_float_safe` with an explicit safety check which `call`s the panic function on failure.
132 /// Not compatible with `scalarize_int_from_float_safe`.
133 expand_int_from_float_safe,
134 /// Replace `int_from_float_optimized_safe` with an explicit safety check which `call`s the panic function on failure.
135 /// Not compatible with `scalarize_int_from_float_optimized_safe`.
136 expand_int_from_float_optimized_safe,
129 /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.137 /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
130 /// Not compatible with `scalarize_add_safe`.138 /// Not compatible with `scalarize_add_safe`.
131 expand_add_safe,139 expand_add_safe,
...@@ -225,6 +233,8 @@ pub const Feature = enum {...@@ -225,6 +233,8 @@ pub const Feature = enum {
225 .trunc => .scalarize_trunc,233 .trunc => .scalarize_trunc,
226 .int_from_float => .scalarize_int_from_float,234 .int_from_float => .scalarize_int_from_float,
227 .int_from_float_optimized => .scalarize_int_from_float_optimized,235 .int_from_float_optimized => .scalarize_int_from_float_optimized,
236 .int_from_float_safe => .scalarize_int_from_float_safe,
237 .int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe,
228 .float_from_int => .scalarize_float_from_int,238 .float_from_int => .scalarize_float_from_int,
229 .shuffle_one => .scalarize_shuffle_one,239 .shuffle_one => .scalarize_shuffle_one,
230 .shuffle_two => .scalarize_shuffle_two,240 .shuffle_two => .scalarize_shuffle_two,
...@@ -439,6 +449,20 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -439,6 +449,20 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
439 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;449 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
440 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);450 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
441 },451 },
452 .int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) {
453 assert(!l.features.has(.scalarize_int_from_float_safe));
454 continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false));
455 } else if (l.features.has(.scalarize_int_from_float_safe)) {
456 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
457 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
458 },
459 .int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) {
460 assert(!l.features.has(.scalarize_int_from_float_optimized_safe));
461 continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true));
462 } else if (l.features.has(.scalarize_int_from_float_optimized_safe)) {
463 const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
464 if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
465 },
442 .block, .loop => {466 .block, .loop => {
443 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;467 const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
444 const extra = l.extraData(Air.Block, ty_pl.payload);468 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...@@ -2001,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
2001 .payload = try l.addBlockBody(main_block.body()),2025 .payload = try l.addBlockBody(main_block.body()),
2002 } };2026 } };
2003}2027}
2028fn safeIntFromFloatBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimized: bool) Error!Air.Inst.Data {
2029 const pt = l.pt;
2030 const zcu = pt.zcu;
2031 const gpa = zcu.gpa;
2032 const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
2033
2034 const operand_ref = ty_op.operand;
2035 const operand_ty = l.typeOf(operand_ref);
2036 const dest_ty = ty_op.ty.toType();
2037
2038 const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
2039 const dest_scalar_ty = dest_ty.scalarType(zcu);
2040 const int_info = dest_scalar_ty.intInfo(zcu);
2041
2042 // We emit 9 instructions in the worst case.
2043 var inst_buf: [9]Air.Inst.Index = undefined;
2044 try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
2045 var main_block: Block = .init(&inst_buf);
2046
2047 // This check is a bit annoying because of floating-point rounding and the fact that this
2048 // builtin truncates. We'll use a bigint for our calculations, because we need to construct
2049 // integers exceeding the bounds of the result integer type, and we need to convert it to a
2050 // float with a specific rounding mode to avoid errors.
2051 // Our bigint may exceed the twos complement limit by one, so add an extra limb.
2052 const limbs = try gpa.alloc(
2053 std.math.big.Limb,
2054 std.math.big.int.calcTwosCompLimbCount(int_info.bits) + 1,
2055 );
2056 defer gpa.free(limbs);
2057 var big: std.math.big.int.Mutable = .init(limbs, 0);
2058
2059 // Check if the operand is lower than `min_int` when truncated to an integer.
2060 big.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
2061 const below_min_inst: Air.Inst.Index = if (!big.positive or big.eqlZero()) bad: {
2062 // `min_int <= 0`, so check for `x <= min_int - 1`.
2063 big.addScalar(big.toConst(), -1);
2064 // For `<=`, we must round the RHS down, so that this value is the first `x` which returns `true`.
2065 const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .floor);
2066 break :bad try main_block.addCmp(l, .lte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
2067 .vector = is_vector,
2068 .optimized = optimized,
2069 });
2070 } else {
2071 // `min_int > 0`, which is currently impossible. It would become possible under #3806, in
2072 // which case we must detect `x < min_int`.
2073 unreachable;
2074 };
2075
2076 // Check if the operand is greater than `max_int` when truncated to an integer.
2077 big.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
2078 const above_max_inst: Air.Inst.Index = if (big.positive or big.eqlZero()) bad: {
2079 // `max_int >= 0`, so check for `x >= max_int + 1`.
2080 big.addScalar(big.toConst(), 1);
2081 // For `>=`, we must round the RHS up, so that this value is the first `x` which returns `true`.
2082 const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .ceil);
2083 break :bad try main_block.addCmp(l, .gte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
2084 .vector = is_vector,
2085 .optimized = optimized,
2086 });
2087 } else {
2088 // `max_int < 0`, which is currently impossible. It would become possible under #3806, in
2089 // which case we must detect `x > max_int`.
2090 unreachable;
2091 };
2092
2093 // Combine the conditions.
2094 const out_of_bounds_inst: Air.Inst.Index = main_block.add(l, .{
2095 .tag = .bool_or,
2096 .data = .{ .bin_op = .{
2097 .lhs = below_min_inst.toRef(),
2098 .rhs = above_max_inst.toRef(),
2099 } },
2100 });
2101 const scalar_out_of_bounds_inst: Air.Inst.Index = if (is_vector) main_block.add(l, .{
2102 .tag = .reduce,
2103 .data = .{ .reduce = .{
2104 .operand = out_of_bounds_inst.toRef(),
2105 .operation = .Or,
2106 } },
2107 }) else out_of_bounds_inst;
2108
2109 // Now emit the actual condbr. "true" will be safety panic. "false" will be "ok", meaning we do
2110 // the `int_from_float` and `br` the result to `orig_inst`.
2111 var condbr: CondBr = .init(l, scalar_out_of_bounds_inst.toRef(), &main_block, .{ .true = .cold });
2112 condbr.then_block = .init(main_block.stealRemainingCapacity());
2113 try condbr.then_block.addPanic(l, .integer_part_out_of_bounds);
2114 condbr.else_block = .init(condbr.then_block.stealRemainingCapacity());
2115 const cast_inst = condbr.else_block.add(l, .{
2116 .tag = if (optimized) .int_from_float_optimized else .int_from_float,
2117 .data = .{ .ty_op = .{
2118 .ty = Air.internedToRef(dest_ty.toIntern()),
2119 .operand = operand_ref,
2120 } },
2121 });
2122 _ = condbr.else_block.add(l, .{
2123 .tag = .br,
2124 .data = .{ .br = .{
2125 .block_inst = orig_inst,
2126 .operand = cast_inst.toRef(),
2127 } },
2128 });
2129 _ = condbr.else_block.stealRemainingCapacity(); // we might not have used it all
2130 try condbr.finish(l);
2131
2132 return .{ .ty_pl = .{
2133 .ty = Air.internedToRef(dest_ty.toIntern()),
2134 .payload = try l.addBlockBody(main_block.body()),
2135 } };
2136}
2004fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {2137fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
2005 const pt = l.pt;2138 const pt = l.pt;
2006 const zcu = pt.zcu;2139 const zcu = pt.zcu;
...@@ -2378,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro...@@ -2378,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
2378 } };2511 } };
2379}2512}
23802513
2514/// Given a `std.math.big.int.Const`, converts it to a `Value` which is a float of type `float_ty`
2515/// representing the same numeric value. If the integer cannot be exactly represented, `round`
2516/// decides whether the value should be rounded up or down. If `is_vector`, then `float_ty` is
2517/// instead a vector of floats, and the result value is a vector containing the converted scalar
2518/// repeated N times.
2519fn floatFromBigIntVal(
2520 pt: Zcu.PerThread,
2521 is_vector: bool,
2522 float_ty: Type,
2523 x: std.math.big.int.Const,
2524 round: std.math.big.int.Round,
2525) Error!Value {
2526 const zcu = pt.zcu;
2527 const scalar_ty = switch (is_vector) {
2528 true => float_ty.childType(zcu),
2529 false => float_ty,
2530 };
2531 assert(scalar_ty.zigTypeTag(zcu) == .float);
2532 const scalar_val: Value = switch (scalar_ty.floatBits(zcu.getTarget())) {
2533 16 => try pt.floatValue(scalar_ty, x.toFloat(f16, round)[0]),
2534 32 => try pt.floatValue(scalar_ty, x.toFloat(f32, round)[0]),
2535 64 => try pt.floatValue(scalar_ty, x.toFloat(f64, round)[0]),
2536 80 => try pt.floatValue(scalar_ty, x.toFloat(f80, round)[0]),
2537 128 => try pt.floatValue(scalar_ty, x.toFloat(f128, round)[0]),
2538 else => unreachable,
2539 };
2540 if (is_vector) {
2541 return .fromInterned(try pt.intern(.{ .aggregate = .{
2542 .ty = float_ty.toIntern(),
2543 .storage = .{ .repeated_elem = scalar_val.toIntern() },
2544 } }));
2545 } else {
2546 return scalar_val;
2547 }
2548}
2549
2381const Block = struct {2550const Block = struct {
2382 instructions: []Air.Inst.Index,2551 instructions: []Air.Inst.Index,
2383 len: usize,2552 len: usize,
...@@ -2735,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");...@@ -2735,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
2735const Legalize = @This();2904const Legalize = @This();
2736const std = @import("std");2905const std = @import("std");
2737const Type = @import("../Type.zig");2906const Type = @import("../Type.zig");
2907const Value = @import("../Value.zig");
2738const Zcu = @import("../Zcu.zig");2908const Zcu = @import("../Zcu.zig");
src/Air/Liveness.zig+4
...@@ -374,6 +374,8 @@ pub fn categorizeOperand(...@@ -374,6 +374,8 @@ pub fn categorizeOperand(
374 .array_to_slice,374 .array_to_slice,
375 .int_from_float,375 .int_from_float,
376 .int_from_float_optimized,376 .int_from_float_optimized,
377 .int_from_float_safe,
378 .int_from_float_optimized_safe,
377 .float_from_int,379 .float_from_int,
378 .get_union_tag,380 .get_union_tag,
379 .clz,381 .clz,
...@@ -1015,6 +1017,8 @@ fn analyzeInst(...@@ -1015,6 +1017,8 @@ fn analyzeInst(
1015 .array_to_slice,1017 .array_to_slice,
1016 .int_from_float,1018 .int_from_float,
1017 .int_from_float_optimized,1019 .int_from_float_optimized,
1020 .int_from_float_safe,
1021 .int_from_float_optimized_safe,
1018 .float_from_int,1022 .float_from_int,
1019 .get_union_tag,1023 .get_union_tag,
1020 .clz,1024 .clz,
src/Air/Liveness/Verify.zig+2
...@@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
107 .array_to_slice,107 .array_to_slice,
108 .int_from_float,108 .int_from_float,
109 .int_from_float_optimized,109 .int_from_float_optimized,
110 .int_from_float_safe,
111 .int_from_float_optimized_safe,
110 .float_from_int,112 .float_from_int,
111 .get_union_tag,113 .get_union_tag,
112 .clz,114 .clz,
src/Air/print.zig+2
...@@ -250,6 +250,8 @@ const Writer = struct {...@@ -250,6 +250,8 @@ const Writer = struct {
250 .splat,250 .splat,
251 .int_from_float,251 .int_from_float,
252 .int_from_float_optimized,252 .int_from_float_optimized,
253 .int_from_float_safe,
254 .int_from_float_optimized_safe,
253 .get_union_tag,255 .get_union_tag,
254 .clz,256 .clz,
255 .ctz,257 .ctz,
src/Air/types_resolved.zig+2
...@@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {...@@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
130 .array_to_slice,130 .array_to_slice,
131 .int_from_float,131 .int_from_float,
132 .int_from_float_optimized,132 .int_from_float_optimized,
133 .int_from_float_safe,
134 .int_from_float_optimized_safe,
133 .float_from_int,135 .float_from_int,
134 .splat,136 .splat,
135 .error_set_has_value,137 .error_set_has_value,
src/Sema.zig+21-31
...@@ -22178,44 +22178,34 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro...@@ -22178,44 +22178,34 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
2217822178
22179 try sema.requireRuntimeBlock(block, src, operand_src);22179 try sema.requireRuntimeBlock(block, src, operand_src);
22180 if (dest_scalar_ty.intInfo(zcu).bits == 0) {22180 if (dest_scalar_ty.intInfo(zcu).bits == 0) {
22181 if (!is_vector) {
22182 if (block.wantSafety()) {
22183 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()));
22184 try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22185 }
22186 return Air.internedToRef((try pt.intValue(dest_ty, 0)).toIntern());
22187 }
22188 if (block.wantSafety()) {22181 if (block.wantSafety()) {
22189 const len = dest_ty.vectorLen(zcu);22182 // Emit an explicit safety check. We can do this one like `abs(x) < 1`.
22190 for (0..len) |i| {22183 const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
22191 const idx_ref = try pt.intRef(.usize, i);22184 const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
22192 const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref);22185 const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
22193 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()));22186 const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
22194 try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);22187 try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
22195 }22188 }
22196 }22189 const scalar_val = try pt.intValue(dest_scalar_ty, 0);
22190 if (!is_vector) return Air.internedToRef(scalar_val.toIntern());
22197 return Air.internedToRef(try pt.intern(.{ .aggregate = .{22191 return Air.internedToRef(try pt.intern(.{ .aggregate = .{
22198 .ty = dest_ty.toIntern(),22192 .ty = dest_ty.toIntern(),
22199 .storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() },22193 .storage = .{ .repeated_elem = scalar_val.toIntern() },
22200 } }));22194 } }));
22201 }22195 }
22202 const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand);
22203 if (block.wantSafety()) {22196 if (block.wantSafety()) {
22204 const back = try block.addTyOp(.float_from_int, operand_ty, result);22197 if (zcu.backendSupportsFeature(.panic_fn)) {
22205 const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back);22198 _ = try sema.preparePanicId(src, .integer_part_out_of_bounds);
22206 const ok = if (is_vector) ok: {22199 }
22207 const ok_pos = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, 1.0))).toIntern()), .lt);22200 return block.addTyOp(switch (block.float_mode) {
22208 const ok_neg = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, -1.0))).toIntern()), .gt);22201 .optimized => .int_from_float_optimized_safe,
22209 const ok = try block.addBinOp(.bit_and, ok_pos, ok_neg);22202 .strict => .int_from_float_safe,
22210 break :ok try block.addReduce(ok, .And);22203 }, dest_ty, operand);
22211 } else ok: {
22212 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()));
22213 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()));
22214 break :ok try block.addBinOp(.bool_and, ok_pos, ok_neg);
22215 };
22216 try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22217 }22204 }
22218 return result;22205 return block.addTyOp(switch (block.float_mode) {
22206 .optimized => .int_from_float_optimized,
22207 .strict => .int_from_float,
22208 }, dest_ty, operand);
22219}22209}
2222022210
22221fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {22211fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
src/arch/aarch64/CodeGen.zig+2
...@@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
861 .sub_safe,861 .sub_safe,
862 .mul_safe,862 .mul_safe,
863 .intcast_safe,863 .intcast_safe,
864 .int_from_float_safe,
865 .int_from_float_optimized_safe,
864 => return self.fail("TODO implement safety_checked_instructions", .{}),866 => return self.fail("TODO implement safety_checked_instructions", .{}),
865867
866 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),868 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/arm/CodeGen.zig+2
...@@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
850 .sub_safe,850 .sub_safe,
851 .mul_safe,851 .mul_safe,
852 .intcast_safe,852 .intcast_safe,
853 .int_from_float_safe,
854 .int_from_float_optimized_safe,
853 => return self.fail("TODO implement safety_checked_instructions", .{}),855 => return self.fail("TODO implement safety_checked_instructions", .{}),
854856
855 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),857 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
src/arch/riscv64/CodeGen.zig+4
...@@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};...@@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};
54pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {54pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
55 return comptime &.initMany(&.{55 return comptime &.initMany(&.{
56 .expand_intcast_safe,56 .expand_intcast_safe,
57 .expand_int_from_float_safe,
58 .expand_int_from_float_optimized_safe,
57 .expand_add_safe,59 .expand_add_safe,
58 .expand_sub_safe,60 .expand_sub_safe,
59 .expand_mul_safe,61 .expand_mul_safe,
...@@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1474 .sub_safe,1476 .sub_safe,
1475 .mul_safe,1477 .mul_safe,
1476 .intcast_safe,1478 .intcast_safe,
1479 .int_from_float_safe,
1480 .int_from_float_optimized_safe,
1477 => return func.fail("TODO implement safety_checked_instructions", .{}),1481 => return func.fail("TODO implement safety_checked_instructions", .{}),
14781482
1479 .cmp_lt,1483 .cmp_lt,
src/arch/sparc64/CodeGen.zig+2
...@@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
696 .sub_safe,696 .sub_safe,
697 .mul_safe,697 .mul_safe,
698 .intcast_safe,698 .intcast_safe,
699 .int_from_float_safe,
700 .int_from_float_optimized_safe,
699 => @panic("TODO implement safety_checked_instructions"),701 => @panic("TODO implement safety_checked_instructions"),
700702
701 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),703 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),
src/arch/wasm/CodeGen.zig+4
...@@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;...@@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
32 return comptime &.initMany(&.{32 return comptime &.initMany(&.{
33 .expand_intcast_safe,33 .expand_intcast_safe,
34 .expand_int_from_float_safe,
35 .expand_int_from_float_optimized_safe,
34 .expand_add_safe,36 .expand_add_safe,
35 .expand_sub_safe,37 .expand_sub_safe,
36 .expand_mul_safe,38 .expand_mul_safe,
...@@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2020 .sub_safe,2022 .sub_safe,
2021 .mul_safe,2023 .mul_safe,
2022 .intcast_safe,2024 .intcast_safe,
2025 .int_from_float_safe,
2026 .int_from_float_optimized_safe,
2023 => return cg.fail("TODO implement safety_checked_instructions", .{}),2027 => return cg.fail("TODO implement safety_checked_instructions", .{}),
20242028
2025 .work_item_id,2029 .work_item_id,
src/arch/x86_64/CodeGen.zig+4
...@@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features...@@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
102 .reduce_one_elem_to_bitcast = true,102 .reduce_one_elem_to_bitcast = true,
103103
104 .expand_intcast_safe = true,104 .expand_intcast_safe = true,
105 .expand_int_from_float_safe = true,
106 .expand_int_from_float_optimized_safe = true,
105 .expand_add_safe = true,107 .expand_add_safe = true,
106 .expand_sub_safe = true,108 .expand_sub_safe = true,
107 .expand_mul_safe = true,109 .expand_mul_safe = true,
...@@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {...@@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
107763 };107765 };
107764 try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);107766 try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);
107765 },107767 },
107768 .int_from_float_safe => unreachable,
107769 .int_from_float_optimized_safe => unreachable,
107766 .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {107770 .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {
107767 const ty_op = air_datas[@intFromEnum(inst)].ty_op;107771 const ty_op = air_datas[@intFromEnum(inst)].ty_op;
107768 var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});107772 var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});
src/codegen/c.zig+4
...@@ -27,6 +27,8 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {...@@ -27,6 +27,8 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
27 inline false, true => |supports_legalize| &.init(.{27 inline false, true => |supports_legalize| &.init(.{
28 // we don't currently ask zig1 to use safe optimization modes28 // we don't currently ask zig1 to use safe optimization modes
29 .expand_intcast_safe = supports_legalize,29 .expand_intcast_safe = supports_legalize,
30 .expand_int_from_float_safe = supports_legalize,
31 .expand_int_from_float_optimized_safe = supports_legalize,
30 .expand_add_safe = supports_legalize,32 .expand_add_safe = supports_legalize,
31 .expand_sub_safe = supports_legalize,33 .expand_sub_safe = supports_legalize,
32 .expand_mul_safe = supports_legalize,34 .expand_mul_safe = supports_legalize,
...@@ -3578,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3578,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3578 .sub_safe,3580 .sub_safe,
3579 .mul_safe,3581 .mul_safe,
3580 .intcast_safe,3582 .intcast_safe,
3583 .int_from_float_safe,
3584 .int_from_float_optimized_safe,
3581 => return f.fail("TODO implement safety_checked_instructions", .{}),3585 => return f.fail("TODO implement safety_checked_instructions", .{}),
35823586
3583 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),3587 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
src/codegen/llvm.zig+6-1
...@@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;...@@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
37const Error = error{ OutOfMemory, CodegenFail };37const Error = error{ OutOfMemory, CodegenFail };
3838
39pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {39pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
40 return null;40 return comptime &.initMany(&.{
41 .expand_int_from_float_safe,
42 .expand_int_from_float_optimized_safe,
43 });
41}44}
4245
43fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {46fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
...@@ -4987,6 +4990,8 @@ pub const FuncGen = struct {...@@ -4987,6 +4990,8 @@ pub const FuncGen = struct {
49874990
4988 .int_from_float => try self.airIntFromFloat(inst, .normal),4991 .int_from_float => try self.airIntFromFloat(inst, .normal),
4989 .int_from_float_optimized => try self.airIntFromFloat(inst, .fast),4992 .int_from_float_optimized => try self.airIntFromFloat(inst, .fast),
4993 .int_from_float_safe => unreachable, // handled by `legalizeFeatures`
4994 .int_from_float_optimized_safe => unreachable, // handled by `legalizeFeatures`
49904995
4991 .array_to_slice => try self.airArrayToSlice(inst),4996 .array_to_slice => try self.airArrayToSlice(inst),
4992 .float_from_int => try self.airFloatFromInt(inst),4997 .float_from_int => try self.airFloatFromInt(inst),
src/codegen/spirv.zig+2
...@@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);...@@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
32 return comptime &.initMany(&.{32 return comptime &.initMany(&.{
33 .expand_intcast_safe,33 .expand_intcast_safe,
34 .expand_int_from_float_safe,
35 .expand_int_from_float_optimized_safe,
34 .expand_add_safe,36 .expand_add_safe,
35 .expand_sub_safe,37 .expand_sub_safe,
36 .expand_mul_safe,38 .expand_mul_safe,
test/behavior/cast.zig+64
...@@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" {...@@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" {
102test "@floatFromInt" {102test "@floatFromInt" {
103 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO103 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
104 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO104 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
105 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
105 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO106 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
106107
107 const S = struct {108 const S = struct {
...@@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {...@@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {
2737 try expect(result[0][0] == 10);2738 try expect(result[0][0] == 10);
2738 try expect(result[0][1] == 20);2739 try expect(result[0][1] == 20);
2739}2740}
2741
2742test "@intFromFloat boundary cases" {
2743 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2744
2745 const S = struct {
2746 fn case(comptime I: type, x: f32, bump: enum { up, down }, expected: I) !void {
2747 const input: f32 = switch (bump) {
2748 .up => std.math.nextAfter(f32, x, std.math.inf(f32)),
2749 .down => std.math.nextAfter(f32, x, -std.math.inf(f32)),
2750 };
2751 const output: I = @intFromFloat(input);
2752 try expect(output == expected);
2753 }
2754 fn doTheTest() !void {
2755 try case(u8, 256.0, .down, 255);
2756 try case(u8, -1.0, .up, 0);
2757 try case(i8, 128.0, .down, 127);
2758 try case(i8, -129.0, .up, -128);
2759
2760 try case(u0, 1.0, .down, 0);
2761 try case(u0, -1.0, .up, 0);
2762 try case(i0, 1.0, .down, 0);
2763 try case(i0, -1.0, .up, 0);
2764
2765 try case(u10, 1024.0, .down, 1023);
2766 try case(u10, -1.0, .up, 0);
2767 try case(i10, 512.0, .down, 511);
2768 try case(i10, -513.0, .up, -512);
2769 }
2770 };
2771 try S.doTheTest();
2772 try comptime S.doTheTest();
2773}
2774
2775test "@intFromFloat vector boundary cases" {
2776 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2777 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
2778 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
2779
2780 const S = struct {
2781 fn case(comptime I: type, unshifted_inputs: [2]f32, expected: [2]I) !void {
2782 const inputs: @Vector(2, f32) = .{
2783 std.math.nextAfter(f32, unshifted_inputs[0], std.math.inf(f32)),
2784 std.math.nextAfter(f32, unshifted_inputs[1], -std.math.inf(f32)),
2785 };
2786 const outputs: @Vector(2, I) = @intFromFloat(inputs);
2787 try expect(outputs[0] == expected[0]);
2788 try expect(outputs[1] == expected[1]);
2789 }
2790 fn doTheTest() !void {
2791 try case(u8, .{ -1.0, 256.0 }, .{ 0, 255 });
2792 try case(i8, .{ -129.0, 128.0 }, .{ -128, 127 });
2793
2794 try case(u0, .{ -1.0, 1.0 }, .{ 0, 0 });
2795 try case(i0, .{ -1.0, 1.0 }, .{ 0, 0 });
2796
2797 try case(u10, .{ -1.0, 1024.0 }, .{ 0, 1023 });
2798 try case(i10, .{ -513.0, 512.0 }, .{ -512, 511 });
2799 }
2800 };
2801 try S.doTheTest();
2802 try comptime S.doTheTest();
2803}
test/cases/safety/@intFromFloat cannot fit - boundary case - i0 max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 1.0;
10pub fn main() !void {
11 _ = @as(i0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - i0 min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -1.0;
10pub fn main() !void {
11 _ = @as(i0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - signed max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 128;
10pub fn main() !void {
11 _ = @as(i8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - signed min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -129;
10pub fn main() !void {
11 _ = @as(i8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - u0 max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 1.0;
10pub fn main() !void {
11 _ = @as(u0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - u0 min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -1.0;
10pub fn main() !void {
11 _ = @as(u0, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = 256;
10pub fn main() !void {
11 _ = @as(u8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - unsigned min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: f32 = -1;
10pub fn main() !void {
11 _ = @as(u8, @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - vector max.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: @Vector(2, f32) = .{ 100, 512 };
10pub fn main() !void {
11 _ = @as(@Vector(2, i10), @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native
test/cases/safety/@intFromFloat cannot fit - boundary case - vector min.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
3 _ = stack_trace;
4 if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
5 std.process.exit(0);
6 }
7 std.process.exit(1);
8}
9var x: @Vector(2, f32) = .{ 100, -513 };
10pub fn main() !void {
11 _ = @as(@Vector(2, i10), @intFromFloat(x));
12 return error.TestFailed;
13}
14// run
15// backend=stage2,llvm
16// target=native