Skip to content

Commit cba479f

Browse files
committed
Add {f32,f64}::approx_unchecked_to<Int> unsafe methods
As discussed in #10184 Currently, casting a floating point number to an integer with `as` is Undefined Behavior if the value is out of range. `-Z saturating-float-casts` fixes this soundness hole by making `as` “saturate” to the maximum or minimum value of the integer type (or zero for `NaN`), but has measurable negative performance impact in some benchmarks. There is some consensus in that thread for enabling saturation by default anyway, but provide an `unsafe fn` alternative for users who know through some other mean that their values are in range.
1 parent f442797 commit cba479f

File tree

7 files changed

+139
-3
lines changed

7 files changed

+139
-3
lines changed

src/libcore/convert/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
4141
#![stable(feature = "rust1", since = "1.0.0")]
4242

43+
mod num;
44+
45+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
46+
pub use num::FloatToInt;
47+
4348
/// The identity function.
4449
///
4550
/// Two things are important to note about this function:

src/libcore/convert/num.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
mod private {
2+
/// This trait being unreachable from outside the crate
3+
/// prevents other implementations of the `FloatToInt` trait,
4+
/// which allows potentially adding more trait methods after the trait is `#[stable]`.
5+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
6+
pub trait Sealed {}
7+
}
8+
9+
/// Supporting trait for inherent methods of `f32` and `f64` such as `round_unchecked_to`.
10+
/// Typically doesn’t need to be used directly.
11+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
12+
pub trait FloatToInt<Int>: private::Sealed + Sized {
13+
#[cfg(not(bootstrap))]
14+
#[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
15+
#[doc(hidden)]
16+
unsafe fn approx_unchecked(self) -> Int;
17+
}
18+
19+
macro_rules! impl_float_to_int {
20+
( $Float: ident => $( $Int: ident )+ ) => {
21+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
22+
impl private::Sealed for $Float {}
23+
$(
24+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
25+
impl FloatToInt<$Int> for $Float {
26+
#[cfg(not(bootstrap))]
27+
#[doc(hidden)]
28+
#[inline]
29+
unsafe fn approx_unchecked(self) -> $Int {
30+
crate::intrinsics::float_to_int_approx_unchecked(self)
31+
}
32+
}
33+
)+
34+
}
35+
}
36+
37+
impl_float_to_int!(f32 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
38+
impl_float_to_int!(f64 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);

src/libcore/intrinsics.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,11 @@ extern "rust-intrinsic" {
11441144
/// May assume inputs are finite.
11451145
pub fn frem_fast<T>(a: T, b: T) -> T;
11461146

1147+
/// Convert with LLVM’s fptoui/fptosi, which may return undef for values out of range
1148+
/// https://github.com/rust-lang/rust/issues/10184
1149+
#[cfg(not(bootstrap))]
1150+
pub fn float_to_int_approx_unchecked<Float, Int>(value: Float) -> Int;
1151+
11471152

11481153
/// Returns the number of bits set in an integer type `T`
11491154
pub fn ctpop<T>(x: T) -> T;

src/libcore/num/f32.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
88
#![stable(feature = "rust1", since = "1.0.0")]
99

10+
#[cfg(not(bootstrap))]
11+
use crate::convert::FloatToInt;
1012
#[cfg(not(test))]
1113
use crate::intrinsics;
12-
1314
use crate::mem;
1415
use crate::num::FpCategory;
1516

@@ -400,6 +401,35 @@ impl f32 {
400401
intrinsics::minnumf32(self, other)
401402
}
402403

404+
/// Rounds toward zero and converts to any primitive integer type,
405+
/// assuming that the value is finite and fits in that type.
406+
///
407+
/// ```
408+
/// #![feature(float_approx_unchecked_to)]
409+
///
410+
/// let value = 4.6_f32;
411+
/// let rounded = unsafe { value.approx_unchecked_to::<u16>() };
412+
/// assert_eq!(rounded, 4);
413+
///
414+
/// let value = -128.9_f32;
415+
/// let rounded = unsafe { value.approx_unchecked_to::<i8>() };
416+
/// assert_eq!(rounded, std::i8::MIN);
417+
/// ```
418+
///
419+
/// # Safety
420+
///
421+
/// The value must:
422+
///
423+
/// * Not be `NaN`
424+
/// * Not be infinite
425+
/// * Be representable in the return type `Int`, after truncating off its fractional part
426+
#[cfg(not(bootstrap))]
427+
#[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
428+
#[inline]
429+
pub unsafe fn approx_unchecked_to<Int>(self) -> Int where Self: FloatToInt<Int> {
430+
FloatToInt::<Int>::approx_unchecked(self)
431+
}
432+
403433
/// Raw transmutation to `u32`.
404434
///
405435
/// This is currently identical to `transmute::<f32, u32>(self)` on all platforms.

src/libcore/num/f64.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
88
#![stable(feature = "rust1", since = "1.0.0")]
99

10+
#[cfg(not(bootstrap))]
11+
use crate::convert::FloatToInt;
1012
#[cfg(not(test))]
1113
use crate::intrinsics;
12-
1314
use crate::mem;
1415
use crate::num::FpCategory;
1516

@@ -413,6 +414,35 @@ impl f64 {
413414
intrinsics::minnumf64(self, other)
414415
}
415416

417+
/// Rounds toward zero and converts to any primitive integer type,
418+
/// assuming that the value is finite and fits in that type.
419+
///
420+
/// ```
421+
/// #![feature(float_approx_unchecked_to)]
422+
///
423+
/// let value = 4.6_f32;
424+
/// let rounded = unsafe { value.approx_unchecked_to::<u16>() };
425+
/// assert_eq!(rounded, 4);
426+
///
427+
/// let value = -128.9_f32;
428+
/// let rounded = unsafe { value.approx_unchecked_to::<i8>() };
429+
/// assert_eq!(rounded, std::i8::MIN);
430+
/// ```
431+
///
432+
/// # Safety
433+
///
434+
/// The value must:
435+
///
436+
/// * Not be `NaN`
437+
/// * Not be infinite
438+
/// * Be representable in the return type `Int`, after truncating off its fractional part
439+
#[cfg(not(bootstrap))]
440+
#[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
441+
#[inline]
442+
pub unsafe fn approx_unchecked_to<Int>(self) -> Int where Self: FloatToInt<Int> {
443+
FloatToInt::<Int>::approx_unchecked(self)
444+
}
445+
416446
/// Raw transmutation to `u64`.
417447
///
418448
/// This is currently identical to `transmute::<f64, u64>(self)` on all platforms.

src/librustc_codegen_llvm/intrinsic.rs

+28-1
Original file line numberDiff line numberDiff line change
@@ -516,9 +516,36 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
516516
return;
517517
}
518518
}
519-
520519
},
521520

521+
"float_to_int_approx_unchecked" => {
522+
if float_type_width(arg_tys[0]).is_none() {
523+
span_invalid_monomorphization_error(
524+
tcx.sess, span,
525+
&format!("invalid monomorphization of `float_to_int_approx_unchecked` \
526+
intrinsic: expected basic float type, \
527+
found `{}`", arg_tys[0]));
528+
return;
529+
}
530+
match int_type_width_signed(ret_ty, self.cx) {
531+
Some((width, signed)) => {
532+
if signed {
533+
self.fptosi(args[0].immediate(), self.cx.type_ix(width))
534+
} else {
535+
self.fptoui(args[0].immediate(), self.cx.type_ix(width))
536+
}
537+
}
538+
None => {
539+
span_invalid_monomorphization_error(
540+
tcx.sess, span,
541+
&format!("invalid monomorphization of `float_to_int_approx_unchecked` \
542+
intrinsic: expected basic integer type, \
543+
found `{}`", ret_ty));
544+
return;
545+
}
546+
}
547+
}
548+
522549
"discriminant_value" => {
523550
args[0].deref(self.cx()).codegen_get_discr(self, ret_ty)
524551
}

src/librustc_typeck/check/intrinsic.rs

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem) {
336336
(1, vec![param(0), param(0)], param(0)),
337337
"fadd_fast" | "fsub_fast" | "fmul_fast" | "fdiv_fast" | "frem_fast" =>
338338
(1, vec![param(0), param(0)], param(0)),
339+
"float_to_int_approx_unchecked" => (2, vec![ param(0) ], param(1)),
339340

340341
"assume" => (0, vec![tcx.types.bool], tcx.mk_unit()),
341342
"likely" => (0, vec![tcx.types.bool], tcx.types.bool),

0 commit comments

Comments
 (0)