@@ -112,6 +112,8 @@ pub const Feature = enum {
112
112
scalarize_trunc ,
113
113
scalarize_int_from_float ,
114
114
scalarize_int_from_float_optimized ,
115
+ scalarize_int_from_float_safe ,
116
+ scalarize_int_from_float_optimized_safe ,
115
117
scalarize_float_from_int ,
116
118
scalarize_shuffle_one ,
117
119
scalarize_shuffle_two ,
@@ -126,6 +128,12 @@ pub const Feature = enum {
126
128
/// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
127
129
/// Not compatible with `scalarize_intcast_safe`.
128
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
137
/// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
130
138
/// Not compatible with `scalarize_add_safe`.
131
139
expand_add_safe ,
@@ -225,6 +233,8 @@ pub const Feature = enum {
225
233
.trunc = > .scalarize_trunc ,
226
234
.int_from_float = > .scalarize_int_from_float ,
227
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
238
.float_from_int = > .scalarize_float_from_int ,
229
239
.shuffle_one = > .scalarize_shuffle_one ,
230
240
.shuffle_two = > .scalarize_shuffle_two ,
@@ -439,6 +449,20 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
439
449
const ty_op = l .air_instructions .items (.data )[@intFromEnum (inst )].ty_op ;
440
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
466
.block , .loop = > {
443
467
const ty_pl = l .air_instructions .items (.data )[@intFromEnum (inst )].ty_pl ;
444
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
2025
.payload = try l .addBlockBody (main_block .body ()),
2002
2026
} };
2003
2027
}
2028
+ fn 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
+ }
2004
2137
fn safeArithmeticBlockPayload (l : * Legalize , orig_inst : Air.Inst.Index , overflow_op_tag : Air.Inst.Tag ) Error ! Air.Inst.Data {
2005
2138
const pt = l .pt ;
2006
2139
const zcu = pt .zcu ;
@@ -2378,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
2378
2511
} };
2379
2512
}
2380
2513
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.
2519
+ fn 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
+
2381
2550
const Block = struct {
2382
2551
instructions : []Air.Inst.Index ,
2383
2552
len : usize ,
@@ -2735,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
2735
2904
const Legalize = @This ();
2736
2905
const std = @import ("std" );
2737
2906
const Type = @import ("../Type.zig" );
2907
+ const Value = @import ("../Value.zig" );
2738
2908
const Zcu = @import ("../Zcu.zig" );
0 commit comments