]> git.lizzy.rs Git - rust.git/commitdiff
Add `{f32,f64}::approx_unchecked_to<Int>` unsafe methods
authorSimon Sapin <simon.sapin@exyr.org>
Thu, 28 Nov 2019 14:24:26 +0000 (15:24 +0100)
committerSimon Sapin <simon.sapin@exyr.org>
Fri, 6 Dec 2019 13:01:05 +0000 (14:01 +0100)
As discussed in https://github.com/rust-lang/rust/issues/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.

src/libcore/convert/mod.rs
src/libcore/convert/num.rs [new file with mode: 0644]
src/libcore/intrinsics.rs
src/libcore/num/f32.rs
src/libcore/num/f64.rs
src/librustc_codegen_llvm/intrinsic.rs
src/librustc_typeck/check/intrinsic.rs

index 08802b3a97a0c0b88a3dc88ee0471856de7ca8c3..16d5375059feebd663c6bc563c96aa750375bd7c 100644 (file)
 
 #![stable(feature = "rust1", since = "1.0.0")]
 
+mod num;
+
+#[unstable(feature = "convert_float_to_int", issue = "67057")]
+pub use num::FloatToInt;
+
 /// The identity function.
 ///
 /// Two things are important to note about this function:
diff --git a/src/libcore/convert/num.rs b/src/libcore/convert/num.rs
new file mode 100644 (file)
index 0000000..076a110
--- /dev/null
@@ -0,0 +1,38 @@
+mod private {
+    /// This trait being unreachable from outside the crate
+    /// prevents other implementations of the `FloatToInt` trait,
+    /// which allows potentially adding more trait methods after the trait is `#[stable]`.
+    #[unstable(feature = "convert_float_to_int", issue = "67057")]
+    pub trait Sealed {}
+}
+
+/// Supporting trait for inherent methods of `f32` and `f64` such as `round_unchecked_to`.
+/// Typically doesn’t need to be used directly.
+#[unstable(feature = "convert_float_to_int", issue = "67057")]
+pub trait FloatToInt<Int>: private::Sealed + Sized {
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
+    #[doc(hidden)]
+    unsafe fn approx_unchecked(self) -> Int;
+}
+
+macro_rules! impl_float_to_int {
+    ( $Float: ident => $( $Int: ident )+ ) => {
+        #[unstable(feature = "convert_float_to_int", issue = "67057")]
+        impl private::Sealed for $Float {}
+        $(
+            #[unstable(feature = "convert_float_to_int", issue = "67057")]
+            impl FloatToInt<$Int> for $Float {
+                #[cfg(not(bootstrap))]
+                #[doc(hidden)]
+                #[inline]
+                unsafe fn approx_unchecked(self) -> $Int {
+                    crate::intrinsics::float_to_int_approx_unchecked(self)
+                }
+            }
+        )+
+    }
+}
+
+impl_float_to_int!(f32 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
+impl_float_to_int!(f64 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
index 19928f30f2ea5907076ad43501fbb4b58c7fd86f..18aae59573d7d56a0707f586617bf86570a816ad 100644 (file)
@@ -1144,6 +1144,11 @@ pub fn volatile_copy_nonoverlapping_memory<T>(dst: *mut T, src: *const T,
     /// May assume inputs are finite.
     pub fn frem_fast<T>(a: T, b: T) -> T;
 
+    /// Convert with LLVM’s fptoui/fptosi, which may return undef for values out of range
+    /// https://github.com/rust-lang/rust/issues/10184
+    #[cfg(not(bootstrap))]
+    pub fn float_to_int_approx_unchecked<Float, Int>(value: Float) -> Int;
+
 
     /// Returns the number of bits set in an integer type `T`
     pub fn ctpop<T>(x: T) -> T;
index 913c0f96d118da09e2d974915fa5a41696415823..ac06f95e244b6f97c407804b0812dac9d151fa12 100644 (file)
@@ -7,9 +7,10 @@
 
 #![stable(feature = "rust1", since = "1.0.0")]
 
+#[cfg(not(bootstrap))]
+use crate::convert::FloatToInt;
 #[cfg(not(test))]
 use crate::intrinsics;
-
 use crate::mem;
 use crate::num::FpCategory;
 
@@ -400,6 +401,35 @@ pub fn min(self, other: f32) -> f32 {
         intrinsics::minnumf32(self, other)
     }
 
+    /// Rounds toward zero and converts to any primitive integer type,
+    /// assuming that the value is finite and fits in that type.
+    ///
+    /// ```
+    /// #![feature(float_approx_unchecked_to)]
+    ///
+    /// let value = 4.6_f32;
+    /// let rounded = unsafe { value.approx_unchecked_to::<u16>() };
+    /// assert_eq!(rounded, 4);
+    ///
+    /// let value = -128.9_f32;
+    /// let rounded = unsafe { value.approx_unchecked_to::<i8>() };
+    /// assert_eq!(rounded, std::i8::MIN);
+    /// ```
+    ///
+    /// # Safety
+    ///
+    /// The value must:
+    ///
+    /// * Not be `NaN`
+    /// * Not be infinite
+    /// * Be representable in the return type `Int`, after truncating off its fractional part
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
+    #[inline]
+    pub unsafe fn approx_unchecked_to<Int>(self) -> Int where Self: FloatToInt<Int> {
+        FloatToInt::<Int>::approx_unchecked(self)
+    }
+
     /// Raw transmutation to `u32`.
     ///
     /// This is currently identical to `transmute::<f32, u32>(self)` on all platforms.
index 6ca830b1f38afa190f8a3b4d4a4fb4df2b756205..794f77fcfc1be379e879214ee5a4cd2672ac9ae8 100644 (file)
@@ -7,9 +7,10 @@
 
 #![stable(feature = "rust1", since = "1.0.0")]
 
+#[cfg(not(bootstrap))]
+use crate::convert::FloatToInt;
 #[cfg(not(test))]
 use crate::intrinsics;
-
 use crate::mem;
 use crate::num::FpCategory;
 
@@ -413,6 +414,35 @@ pub fn min(self, other: f64) -> f64 {
         intrinsics::minnumf64(self, other)
     }
 
+    /// Rounds toward zero and converts to any primitive integer type,
+    /// assuming that the value is finite and fits in that type.
+    ///
+    /// ```
+    /// #![feature(float_approx_unchecked_to)]
+    ///
+    /// let value = 4.6_f32;
+    /// let rounded = unsafe { value.approx_unchecked_to::<u16>() };
+    /// assert_eq!(rounded, 4);
+    ///
+    /// let value = -128.9_f32;
+    /// let rounded = unsafe { value.approx_unchecked_to::<i8>() };
+    /// assert_eq!(rounded, std::i8::MIN);
+    /// ```
+    ///
+    /// # Safety
+    ///
+    /// The value must:
+    ///
+    /// * Not be `NaN`
+    /// * Not be infinite
+    /// * Be representable in the return type `Int`, after truncating off its fractional part
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
+    #[inline]
+    pub unsafe fn approx_unchecked_to<Int>(self) -> Int where Self: FloatToInt<Int> {
+        FloatToInt::<Int>::approx_unchecked(self)
+    }
+
     /// Raw transmutation to `u64`.
     ///
     /// This is currently identical to `transmute::<f64, u64>(self)` on all platforms.
index 9df75a800f1239a971a56e771181aace6cd88fc6..1767ad118e7c0912bd706b9527caabbbdbdaaeee 100644 (file)
@@ -516,9 +516,36 @@ fn codegen_intrinsic_call(
                         return;
                     }
                 }
-
             },
 
+            "float_to_int_approx_unchecked" => {
+                if float_type_width(arg_tys[0]).is_none() {
+                    span_invalid_monomorphization_error(
+                        tcx.sess, span,
+                        &format!("invalid monomorphization of `float_to_int_approx_unchecked` \
+                                  intrinsic: expected basic float type, \
+                                  found `{}`", arg_tys[0]));
+                    return;
+                }
+                match int_type_width_signed(ret_ty, self.cx) {
+                    Some((width, signed)) => {
+                        if signed {
+                            self.fptosi(args[0].immediate(), self.cx.type_ix(width))
+                        } else {
+                            self.fptoui(args[0].immediate(), self.cx.type_ix(width))
+                        }
+                    }
+                    None => {
+                        span_invalid_monomorphization_error(
+                            tcx.sess, span,
+                            &format!("invalid monomorphization of `float_to_int_approx_unchecked` \
+                                      intrinsic:  expected basic integer type, \
+                                      found `{}`", ret_ty));
+                        return;
+                    }
+                }
+            }
+
             "discriminant_value" => {
                 args[0].deref(self.cx()).codegen_get_discr(self, ret_ty)
             }
index 9f034e65b6eaa6a5faa04f20010ee850de1c7c40..b967c6e36e35ebc209ba4f7d73e69a42254e1655 100644 (file)
@@ -336,6 +336,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem) {
                 (1, vec![param(0), param(0)], param(0)),
             "fadd_fast" | "fsub_fast" | "fmul_fast" | "fdiv_fast" | "frem_fast" =>
                 (1, vec![param(0), param(0)], param(0)),
+            "float_to_int_approx_unchecked" => (2, vec![ param(0) ], param(1)),
 
             "assume" => (0, vec![tcx.types.bool], tcx.mk_unit()),
             "likely" => (0, vec![tcx.types.bool], tcx.types.bool),