From d9168a0c464b6791600080fe4fdb04918e49b78d Mon Sep 17 00:00:00 2001
From: Trevor Gross <tmgross@umich.edu>
Date: Mon, 4 Nov 2024 18:19:49 -0600
Subject: [PATCH] Add integer to `f16` conversions

These are not present in LLVM's `compiler-rt` but LLVM does emit them in
some cases [1].

[1]: https://github.com/rust-lang/rust/issues/132614#issuecomment-2455910165
---
 README.md               |   6 ++
 src/float/conv.rs       | 152 +++++++++++++++++++++++++++++++++-------
 testcrate/Cargo.toml    |   3 +-
 testcrate/build.rs      |   6 ++
 testcrate/tests/conv.rs |  12 +++-
 5 files changed, 153 insertions(+), 26 deletions(-)

diff --git a/README.md b/README.md
index a2b38cce0..4c064a22b 100644
--- a/README.md
+++ b/README.md
@@ -233,11 +233,17 @@ of being added to Rust.
 - [x] fixunstfdi.c
 - [x] fixunstfsi.c
 - [x] fixunstfti.c
+- [x] floatdihf.c (not yet in `compiler-rt` but emitted by LLVM)
 - [x] floatditf.c
+- [x] floatsihf.c (not yet in `compiler-rt` but emitted by LLVM)
 - [x] floatsitf.c
+- [x] floattihf.c (not yet in `compiler-rt` but emitted by LLVM)
 - [x] floattitf.c
+- [x] floatundihf.c (not yet in `compiler-rt` but emitted by LLVM)
 - [x] floatunditf.c
+- [x] floatunsihf.c (not yet in `compiler-rt` but emitted by LLVM)
 - [x] floatunsitf.c
+- [x] floatuntihf.c (not yet in `compiler-rt` but emitted by LLVM)
 - [x] floatuntitf.c
 - [x] multf3.c
 - [x] powitf2.c
diff --git a/src/float/conv.rs b/src/float/conv.rs
index 4aea67c91..5e9b995f1 100644
--- a/src/float/conv.rs
+++ b/src/float/conv.rs
@@ -79,6 +79,26 @@ mod int_to_float {
         F::from_bits(conv(i.unsigned_abs()) | sign_bit)
     }
 
+    #[cfg(f16_enabled)]
+    pub fn u32_to_f16_bits(i: u32) -> u16 {
+        let n = i.leading_zeros();
+        let i_m = i.wrapping_shl(n);
+        // Mantissa with implicit bit set
+        let m_base: u16 = (i_m >> shift_f_lt_i::<u32, f16>()) as u16;
+        // The entire lower half of `i` will be truncated (masked portion), plus the
+        // next `EXPONENT_BITS` bits.
+        let adj = (i_m >> f16::EXPONENT_BITS | i_m & 0xFF) as u16;
+        let m = m_adj::<f16>(m_base, adj);
+        let e = if i == 0 { 0 } else { exp::<u32, f16>(n) - 1 };
+        // Any int can have an exponent out of range for `f16`, unlike other float types.
+        // Clamp this.
+        if e >= f16::EXPONENT_MAX as u16 - 1 {
+            f16::INFINITY.to_bits()
+        } else {
+            repr::<f16>(e, m)
+        }
+    }
+
     pub fn u32_to_f32_bits(i: u32) -> u32 {
         if i == 0 {
             return 0;
@@ -122,6 +142,33 @@ mod int_to_float {
         (h as u128) << 64
     }
 
+    #[cfg(f16_enabled)]
+    pub fn u64_to_f16_bits(i: u64) -> u16 {
+        let n = i.leading_zeros();
+        let i_m = i.wrapping_shl(n); // Mantissa, shifted so the first bit is nonzero
+        let m_base: u16 = (i_m >> shift_f_lt_i::<u64, f16>()) as u16;
+
+        // Within the upper `F::BITS`, everything except for the signifcand
+        // gets truncated
+        let d1: u16 = (i_m >> (u64::BITS - f16::BITS - f16::SIGNIFICAND_BITS - 1)).cast();
+
+        // The entire rest of `i_m` gets truncated. Zero the upper `F::BITS` then just
+        // check if it is nonzero.
+        let d2: u16 = (i_m << f16::BITS >> f16::BITS != 0).into();
+        let adj = d1 | d2;
+
+        // Mantissa with implicit bit set
+        let m = m_adj::<f16>(m_base, adj);
+        let e = if i == 0 { 0 } else { exp::<u64, f16>(n) - 1 };
+
+        // Clamp to infinity if the exponent is out of range
+        if e >= f16::EXPONENT_MAX as u16 - 1 {
+            f16::INFINITY.to_bits()
+        } else {
+            repr::<f16>(e, m)
+        }
+    }
+
     pub fn u64_to_f32_bits(i: u64) -> u32 {
         let n = i.leading_zeros();
         let i_m = i.wrapping_shl(n);
@@ -160,6 +207,33 @@ mod int_to_float {
         repr::<f128>(e, m)
     }
 
+    #[cfg(f16_enabled)]
+    pub fn u128_to_f16_bits(i: u128) -> u16 {
+        let n = i.leading_zeros();
+        let i_m = i.wrapping_shl(n); // Mantissa, shifted so the first bit is nonzero
+        let m_base: u16 = (i_m >> shift_f_lt_i::<u128, f16>()) as u16;
+
+        // Within the upper `F::BITS`, everything except for the signifcand
+        // gets truncated
+        let d1: u16 = (i_m >> (u128::BITS - f16::BITS - f16::SIGNIFICAND_BITS - 1)).cast();
+
+        // The entire rest of `i_m` gets truncated. Zero the upper `F::BITS` then just
+        // check if it is nonzero.
+        let d2: u16 = (i_m << f16::BITS >> f16::BITS != 0).into();
+        let adj = d1 | d2;
+
+        // Mantissa with implicit bit set
+        let m = m_adj::<f16>(m_base, adj);
+        let e = if i == 0 { 0 } else { exp::<u128, f16>(n) - 1 };
+
+        // Clamp to infinity if the exponent is out of range
+        if e >= f16::EXPONENT_MAX as u16 - 1 {
+            f16::INFINITY.to_bits()
+        } else {
+            repr::<f16>(e, m)
+        }
+    }
+
     pub fn u128_to_f32_bits(i: u128) -> u32 {
         let n = i.leading_zeros();
         let i_m = i.wrapping_shl(n); // Mantissa, shifted so the first bit is nonzero
@@ -210,6 +284,11 @@ mod int_to_float {
 
 // Conversions from unsigned integers to floats.
 intrinsics! {
+    #[cfg(f16_enabled)]
+    pub extern "C" fn __floatunsihf(i: u32) -> f16 {
+        f16::from_bits(int_to_float::u32_to_f16_bits(i))
+    }
+
     #[arm_aeabi_alias = __aeabi_ui2f]
     pub extern "C" fn __floatunsisf(i: u32) -> f32 {
         f32::from_bits(int_to_float::u32_to_f32_bits(i))
@@ -220,6 +299,17 @@ intrinsics! {
         f64::from_bits(int_to_float::u32_to_f64_bits(i))
     }
 
+    #[ppc_alias = __floatunsikf]
+    #[cfg(f128_enabled)]
+    pub extern "C" fn __floatunsitf(i: u32) -> f128 {
+        f128::from_bits(int_to_float::u32_to_f128_bits(i))
+    }
+
+    #[cfg(f16_enabled)]
+    pub extern "C" fn __floatundihf(i: u64) -> f16 {
+        f16::from_bits(int_to_float::u64_to_f16_bits(i))
+    }
+
     #[arm_aeabi_alias = __aeabi_ul2f]
     pub extern "C" fn __floatundisf(i: u64) -> f32 {
         f32::from_bits(int_to_float::u64_to_f32_bits(i))
@@ -230,6 +320,17 @@ intrinsics! {
         f64::from_bits(int_to_float::u64_to_f64_bits(i))
     }
 
+    #[ppc_alias = __floatundikf]
+    #[cfg(f128_enabled)]
+    pub extern "C" fn __floatunditf(i: u64) -> f128 {
+        f128::from_bits(int_to_float::u64_to_f128_bits(i))
+    }
+
+    #[cfg(f16_enabled)]
+    pub extern "C" fn __floatuntihf(i: u128) -> f16 {
+        f16::from_bits(int_to_float::u128_to_f16_bits(i))
+    }
+
     #[cfg_attr(target_os = "uefi", unadjusted_on_win64)]
     pub extern "C" fn __floatuntisf(i: u128) -> f32 {
         f32::from_bits(int_to_float::u128_to_f32_bits(i))
@@ -240,18 +341,6 @@ intrinsics! {
         f64::from_bits(int_to_float::u128_to_f64_bits(i))
     }
 
-    #[ppc_alias = __floatunsikf]
-    #[cfg(f128_enabled)]
-    pub extern "C" fn __floatunsitf(i: u32) -> f128 {
-        f128::from_bits(int_to_float::u32_to_f128_bits(i))
-    }
-
-    #[ppc_alias = __floatundikf]
-    #[cfg(f128_enabled)]
-    pub extern "C" fn __floatunditf(i: u64) -> f128 {
-        f128::from_bits(int_to_float::u64_to_f128_bits(i))
-    }
-
     #[ppc_alias = __floatuntikf]
     #[cfg(f128_enabled)]
     pub extern "C" fn __floatuntitf(i: u128) -> f128 {
@@ -261,6 +350,11 @@ intrinsics! {
 
 // Conversions from signed integers to floats.
 intrinsics! {
+    #[cfg(f16_enabled)]
+    pub extern "C" fn __floatsihf(i: i32) -> f16 {
+        int_to_float::signed(i, int_to_float::u32_to_f16_bits)
+    }
+
     #[arm_aeabi_alias = __aeabi_i2f]
     pub extern "C" fn __floatsisf(i: i32) -> f32 {
         int_to_float::signed(i, int_to_float::u32_to_f32_bits)
@@ -271,6 +365,17 @@ intrinsics! {
         int_to_float::signed(i, int_to_float::u32_to_f64_bits)
     }
 
+    #[ppc_alias = __floatsikf]
+    #[cfg(f128_enabled)]
+    pub extern "C" fn __floatsitf(i: i32) -> f128 {
+        int_to_float::signed(i, int_to_float::u32_to_f128_bits)
+    }
+
+    #[cfg(f16_enabled)]
+    pub extern "C" fn __floatdihf(i: i64) -> f16 {
+        int_to_float::signed(i, int_to_float::u64_to_f16_bits)
+    }
+
     #[arm_aeabi_alias = __aeabi_l2f]
     pub extern "C" fn __floatdisf(i: i64) -> f32 {
         int_to_float::signed(i, int_to_float::u64_to_f32_bits)
@@ -281,6 +386,17 @@ intrinsics! {
         int_to_float::signed(i, int_to_float::u64_to_f64_bits)
     }
 
+    #[ppc_alias = __floatdikf]
+    #[cfg(f128_enabled)]
+    pub extern "C" fn __floatditf(i: i64) -> f128 {
+        int_to_float::signed(i, int_to_float::u64_to_f128_bits)
+    }
+
+    #[cfg(f16_enabled)]
+    pub extern "C" fn __floattihf(i: i128) -> f16 {
+        int_to_float::signed(i, int_to_float::u128_to_f16_bits)
+    }
+
     #[cfg_attr(target_os = "uefi", unadjusted_on_win64)]
     pub extern "C" fn __floattisf(i: i128) -> f32 {
         int_to_float::signed(i, int_to_float::u128_to_f32_bits)
@@ -291,18 +407,6 @@ intrinsics! {
         int_to_float::signed(i, int_to_float::u128_to_f64_bits)
     }
 
-    #[ppc_alias = __floatsikf]
-    #[cfg(f128_enabled)]
-    pub extern "C" fn __floatsitf(i: i32) -> f128 {
-        int_to_float::signed(i, int_to_float::u32_to_f128_bits)
-    }
-
-    #[ppc_alias = __floatdikf]
-    #[cfg(f128_enabled)]
-    pub extern "C" fn __floatditf(i: i64) -> f128 {
-        int_to_float::signed(i, int_to_float::u64_to_f128_bits)
-    }
-
     #[ppc_alias = __floattikf]
     #[cfg(f128_enabled)]
     pub extern "C" fn __floattitf(i: i128) -> f128 {
diff --git a/testcrate/Cargo.toml b/testcrate/Cargo.toml
index 21cec1701..2299eda83 100644
--- a/testcrate/Cargo.toml
+++ b/testcrate/Cargo.toml
@@ -39,9 +39,10 @@ no-f16-f128 = ["compiler_builtins/no-f16-f128"]
 mem = ["compiler_builtins/mem"]
 mangled-names = ["compiler_builtins/mangled-names"]
 # Skip tests that rely on f128 symbols being available on the system
-no-sys-f128 = ["no-sys-f128-int-convert", "no-sys-f16-f128-convert"]
+no-sys-f128 = ["no-sys-f16-int-convert", "no-sys-f128-int-convert", "no-sys-f16-f128-convert"]
 # Some platforms have some f128 functions but everything except integer conversions
 no-sys-f128-int-convert = []
+no-sys-f16-int-convert = []
 no-sys-f16-f128-convert = []
 # Skip tests that rely on f16 symbols being available on the system
 no-sys-f16 = []
diff --git a/testcrate/build.rs b/testcrate/build.rs
index 6205c7ac6..8da4d5593 100644
--- a/testcrate/build.rs
+++ b/testcrate/build.rs
@@ -6,6 +6,7 @@ enum Feature {
     NoSysF128,
     NoSysF128IntConvert,
     NoSysF16,
+    NoSysF16IntConvert,
     NoSysF16F128Convert,
 }
 
@@ -40,6 +41,7 @@ fn main() {
     {
         features.insert(Feature::NoSysF128);
         features.insert(Feature::NoSysF128IntConvert);
+        features.insert(Feature::NoSysF16IntConvert);
         features.insert(Feature::NoSysF16F128Convert);
     }
 
@@ -76,6 +78,10 @@ fn main() {
                 "no-sys-f128-int-convert",
                 "using apfloat fallback for f128 <-> int conversions",
             ),
+            Feature::NoSysF16IntConvert => (
+                "no-sys-f16-int-convert",
+                "using apfloat fallback for f16 <-> int conversions",
+            ),
             Feature::NoSysF16F128Convert => (
                 "no-sys-f16-f128-convert",
                 "using apfloat fallback for f16 <-> f128 conversions",
diff --git a/testcrate/tests/conv.rs b/testcrate/tests/conv.rs
index a08748af7..0b8c85f8a 100644
--- a/testcrate/tests/conv.rs
+++ b/testcrate/tests/conv.rs
@@ -34,7 +34,7 @@ mod i_to_f {
                                     FloatTy::from_u128(x.try_into().unwrap()).value
                                 };
 
-                                <$f_ty>::from_bits(apf.to_bits())
+                                <$f_ty>::from_bits(apf.to_bits().try_into().unwrap())
                             },
                             x
                         );
@@ -100,6 +100,16 @@ mod i_to_f {
         };
     }
 
+    #[cfg(f16_enabled)]
+    i_to_f! { f16, Half, not(feature = "no-sys-f16-int-convert"),
+        u32, __floatunsihf;
+        i32, __floatsihf;
+        u64, __floatundihf;
+        i64, __floatdihf;
+        u128, __floatuntihf;
+        i128, __floattihf;
+    }
+
     i_to_f! { f32, Single, all(),
         u32, __floatunsisf;
         i32, __floatsisf;