Repository: bytecodealliance/wasmtime
Pull request #8860 (2024-06-21 21:50:04)

Initial `f16` and `f128` support
Issue #8312

This PR adds initial basic support for `f16` and `f128` to Cranelift. This is enough to allow the interpreter to bitcast to and from `f16`s and `f128`s, as shown in the added filetest.


Patch:
diff --git a/cranelift/codegen/meta/src/cdsl/types.rs b/cranelift/codegen/meta/src/cdsl/types.rs
index 0ff486609..28b23132d 100644
--- a/cranelift/codegen/meta/src/cdsl/types.rs
+++ b/cranelift/codegen/meta/src/cdsl/types.rs
@@ -136,6 +136,12 @@ impl LaneType {
     /// Return a string containing the documentation comment for this lane type.
     pub fn doc(self) -> String {
         match self {
+            LaneType::Float(shared_types::Float::F16) => String::from(
+                "A 16-bit floating point type represented in the IEEE 754-2008
+                *binary16* interchange format. This corresponds to the :c:type:`_Float16`
+                type in most C implementations.
+                WARNING: f16 support is a work-in-progress and is incomplete",
+            ),
             LaneType::Float(shared_types::Float::F32) => String::from(
                 "A 32-bit floating point type represented in the IEEE 754-2008
                 *binary32* interchange format. This corresponds to the :c:type:`float`
@@ -146,6 +152,12 @@ impl LaneType {
                 *binary64* interchange format. This corresponds to the :c:type:`double`
                 type in most C implementations.",
             ),
+            LaneType::Float(shared_types::Float::F128) => String::from(
+                "A 128-bit floating point type represented in the IEEE 754-2008
+                *binary128* interchange format. This corresponds to the :c:type:`_Float128`
+                type in most C implementations.
+                WARNING: f128 support is a work-in-progress and is incomplete",
+            ),
             LaneType::Int(_) if self.lane_bits() < 32 => format!(
                 "An integer type with {} bits.
                 WARNING: arithmetic on {}bit integers is incomplete",
@@ -168,13 +180,15 @@ impl LaneType {
     pub fn number(self) -> u16 {
         constants::LANE_BASE
             + match self {
-                LaneType::Int(shared_types::Int::I8) => 6,
-                LaneType::Int(shared_types::Int::I16) => 7,
-                LaneType::Int(shared_types::Int::I32) => 8,
-                LaneType::Int(shared_types::Int::I64) => 9,
-                LaneType::Int(shared_types::Int::I128) => 10,
-                LaneType::Float(shared_types::Float::F32) => 11,
-                LaneType::Float(shared_types::Float::F64) => 12,
+                LaneType::Int(shared_types::Int::I8) => 4,
+                LaneType::Int(shared_types::Int::I16) => 5,
+                LaneType::Int(shared_types::Int::I32) => 6,
+                LaneType::Int(shared_types::Int::I64) => 7,
+                LaneType::Int(shared_types::Int::I128) => 8,
+                LaneType::Float(shared_types::Float::F16) => 9,
+                LaneType::Float(shared_types::Float::F32) => 10,
+                LaneType::Float(shared_types::Float::F64) => 11,
+                LaneType::Float(shared_types::Float::F128) => 12,
             }
     }
 
@@ -191,8 +205,10 @@ impl LaneType {
 
     pub fn float_from_bits(num_bits: u16) -> LaneType {
         LaneType::Float(match num_bits {
+            16 => shared_types::Float::F16,
             32 => shared_types::Float::F32,
             64 => shared_types::Float::F64,
+            128 => shared_types::Float::F128,
             _ => unreachable!("unexpected num bits for float"),
         })
     }
diff --git a/cranelift/codegen/meta/src/cdsl/typevar.rs b/cranelift/codegen/meta/src/cdsl/typevar.rs
index da0e92cd8..d0a331369 100644
--- a/cranelift/codegen/meta/src/cdsl/typevar.rs
+++ b/cranelift/codegen/meta/src/cdsl/typevar.rs
@@ -9,7 +9,7 @@ use crate::cdsl::types::{LaneType, ReferenceType, ValueType};
 
 const MAX_LANES: u16 = 256;
 const MAX_BITS: u16 = 128;
-const MAX_FLOAT_BITS: u16 = 64;
+const MAX_FLOAT_BITS: u16 = 128;
 
 /// Type variables can be used in place of concrete types when defining
 /// instructions. This makes the instructions *polymorphic*.
@@ -159,7 +159,7 @@ impl TypeVar {
                     "can't halve all integer types"
                 );
                 assert!(
-                    ts.floats.is_empty() || *ts.floats.iter().min().unwrap() > 32,
+                    ts.floats.is_empty() || *ts.floats.iter().min().unwrap() > 16,
                     "can't halve all float types"
                 );
             }
@@ -179,7 +179,7 @@ impl TypeVar {
                     "can't halve all integer types"
                 );
                 assert!(
-                    ts.floats.is_empty() || *ts.floats.iter().min().unwrap() > 32,
+                    ts.floats.is_empty() || *ts.floats.iter().min().unwrap() > 16,
                     "can't halve all float types"
                 );
                 assert!(
@@ -464,7 +464,7 @@ impl TypeSet {
     fn half_width(&self) -> TypeSet {
         let mut copy = self.clone();
         copy.ints = NumSet::from_iter(self.ints.iter().filter(|&&x| x > 8).map(|&x| x / 2));
-        copy.floats = NumSet::from_iter(self.floats.iter().filter(|&&x| x > 32).map(|&x| x / 2));
+        copy.floats = NumSet::from_iter(self.floats.iter().filter(|&&x| x > 16).map(|&x| x / 2));
         copy
     }
 
@@ -643,7 +643,7 @@ impl TypeSetBuilder {
             range_to_set(self.simd_lanes.to_range(min_lanes..MAX_LANES, Some(1))),
             range_to_set(self.dynamic_simd_lanes.to_range(2..MAX_LANES, None)),
             range_to_set(self.ints.to_range(8..MAX_BITS, None)),
-            range_to_set(self.floats.to_range(32..64, None)),
+            range_to_set(self.floats.to_range(16..MAX_FLOAT_BITS, None)),
             range_to_set(self.refs.to_range(32..64, None)),
         )
     }
@@ -711,7 +711,7 @@ fn test_typevar_builder() {
 
     let type_set = TypeSetBuilder::new().floats(Interval::All).build();
     assert_eq!(type_set.lanes, num_set![1]);
-    assert_eq!(type_set.floats, num_set![32, 64]);
+    assert_eq!(type_set.floats, num_set![16, 32, 64, 128]);
     assert!(type_set.ints.is_empty());
 
     let type_set = TypeSetBuilder::new()
@@ -720,7 +720,7 @@ fn test_typevar_builder() {
         .includes_scalars(false)
         .build();
     assert_eq!(type_set.lanes, num_set![2, 4, 8, 16, 32, 64, 128, 256]);
-    assert_eq!(type_set.floats, num_set![32, 64]);
+    assert_eq!(type_set.floats, num_set![16, 32, 64, 128]);
     assert!(type_set.ints.is_empty());
 
     let type_set = TypeSetBuilder::new()
@@ -729,7 +729,7 @@ fn test_typevar_builder() {
         .includes_scalars(true)
         .build();
     assert_eq!(type_set.lanes, num_set![1, 2, 4, 8, 16, 32, 64, 128, 256]);
-    assert_eq!(type_set.floats, num_set![32, 64]);
+    assert_eq!(type_set.floats, num_set![16, 32, 64, 128]);
     assert!(type_set.ints.is_empty());
 
     let type_set = TypeSetBuilder::new()
@@ -738,7 +738,7 @@ fn test_typevar_builder() {
         .includes_scalars(false)
         .build();
     assert_eq!(type_set.lanes, num_set![2, 4, 8, 16, 32, 64, 128, 256]);
-    assert_eq!(type_set.floats, num_set![32, 64]);
+    assert_eq!(type_set.floats, num_set![16, 32, 64, 128]);
     assert!(type_set.dynamic_lanes.is_empty());
     assert!(type_set.ints.is_empty());
 
@@ -753,7 +753,7 @@ fn test_typevar_builder() {
         num_set![2, 4, 8, 16, 32, 64, 128, 256]
     );
     assert_eq!(type_set.ints, num_set![8, 16, 32, 64, 128]);
-    assert_eq!(type_set.floats, num_set![32, 64]);
+    assert_eq!(type_set.floats, num_set![16, 32, 64, 128]);
     assert_eq!(type_set.lanes, num_set![1]);
 
     let type_set = TypeSetBuilder::new()
@@ -765,7 +765,7 @@ fn test_typevar_builder() {
         type_set.dynamic_lanes,
         num_set![2, 4, 8, 16, 32, 64, 128, 256]
     );
-    assert_eq!(type_set.floats, num_set![32, 64]);
+    assert_eq!(type_set.floats, num_set![16, 32, 64, 128]);
     assert_eq!(type_set.lanes, num_set![1]);
     assert!(type_set.ints.is_empty());
 
@@ -871,12 +871,12 @@ fn test_forward_images() {
         TypeSetBuilder::new().ints(8..16).build()
     );
     assert_eq!(
-        TypeSetBuilder::new().floats(32..32).build().half_width(),
+        TypeSetBuilder::new().floats(16..16).build().half_width(),
         empty_set
     );
     assert_eq!(
-        TypeSetBuilder::new().floats(32..64).build().half_width(),
-        TypeSetBuilder::new().floats(32..32).build()
+        TypeSetBuilder::new().floats(32..128).build().half_width(),
+        TypeSetBuilder::new().floats(16..64).build()
     );
 
     // Double width.
@@ -893,8 +893,8 @@ fn test_forward_images() {
         TypeSetBuilder::new().floats(64..64).build()
     );
     assert_eq!(
-        TypeSetBuilder::new().floats(32..64).build().double_width(),
-        TypeSetBuilder::new().floats(64..64).build()
+        TypeSetBuilder::new().floats(16..64).build().double_width(),
+        TypeSetBuilder::new().floats(32..128).build()
     );
 }
 
diff --git a/cranelift/codegen/meta/src/shared/types.rs b/cranelift/codegen/meta/src/shared/types.rs
index 33efd1080..3bd2e2d55 100644
--- a/cranelift/codegen/meta/src/shared/types.rs
+++ b/cranelift/codegen/meta/src/shared/types.rs
@@ -43,8 +43,10 @@ impl Iterator for IntIterator {
 
 #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
 pub(crate) enum Float {
+    F16 = 16,
     F32 = 32,
     F64 = 64,
+    F128 = 128,
 }
 
 /// Iterator through the variants of the Float enum.
@@ -63,8 +65,10 @@ impl Iterator for FloatIterator {
     type Item = Float;
     fn next(&mut self) -> Option<Self::Item> {
         let res = match self.index {
-            0 => Some(Float::F32),
-            1 => Some(Float::F64),
+            0 => Some(Float::F16),
+            1 => Some(Float::F32),
+            2 => Some(Float::F64),
+            3 => Some(Float::F128),
             _ => return None,
         };
         self.index += 1;
@@ -122,8 +126,10 @@ mod iter_tests {
     #[test]
     fn float_iter_works() {
         let mut float_iter = FloatIterator::new();
+        assert_eq!(float_iter.next(), Some(Float::F16));
         assert_eq!(float_iter.next(), Some(Float::F32));
         assert_eq!(float_iter.next(), Some(Float::F64));
+        assert_eq!(float_iter.next(), Some(Float::F128));
         assert_eq!(float_iter.next(), None);
     }
 
diff --git a/cranelift/codegen/src/data_value.rs b/cranelift/codegen/src/data_value.rs
index 25eb7a401..e2feb4946 100644
--- a/cranelift/codegen/src/data_value.rs
+++ b/cranelift/codegen/src/data_value.rs
@@ -1,7 +1,8 @@
 //! This module gives users to instantiate values that Cranelift understands. These values are used,
 //! for example, during interpretation and for wrapping immediates.
-use crate::ir::immediates::{Ieee32, Ieee64, Offset32};
+use crate::ir::immediates::{Ieee128, Ieee16, Ieee32, Ieee64, Offset32};
 use crate::ir::{types, ConstantData, Type};
+use core::cmp::Ordering;
 use core::fmt::{self, Display, Formatter};
 
 /// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value
@@ -16,8 +17,10 @@ pub enum DataValue {
     I32(i32),
     I64(i64),
     I128(i128),
+    F16(Ieee16),
     F32(Ieee32),
     F64(Ieee64),
+    F128(Ieee128),
     V128([u8; 16]),
     V64([u8; 8]),
 }
@@ -36,10 +39,14 @@ impl PartialEq for DataValue {
             (I64(_), _) => false,
             (I128(l), I128(r)) => l == r,
             (I128(_), _) => false,
+            (F16(l), F16(r)) => l.partial_cmp(&r) == Some(Ordering::Equal),
+            (F16(_), _) => false,
             (F32(l), F32(r)) => l.as_f32() == r.as_f32(),
             (F32(_), _) => false,
             (F64(l), F64(r)) => l.as_f64() == r.as_f64(),
             (F64(_), _) => false,
+            (F128(l), F128(r)) => l.partial_cmp(&r) == Some(Ordering::Equal),
+            (F128(_), _) => false,
             (V128(l), V128(r)) => l == r,
             (V128(_), _) => false,
             (V64(l), V64(r)) => l == r,
@@ -70,8 +77,10 @@ impl DataValue {
             DataValue::I32(_) => types::I32,
             DataValue::I64(_) => types::I64,
             DataValue::I128(_) => types::I128,
+            DataValue::F16(_) => types::F16,
             DataValue::F32(_) => types::F32,
             DataValue::F64(_) => types::F64,
+            DataValue::F128(_) => types::F128,
             DataValue::V128(_) => types::I8X16, // A default type.
             DataValue::V64(_) => types::I8X8,   // A default type.
         }
@@ -92,8 +101,10 @@ impl DataValue {
             DataValue::I32(i) => DataValue::I32(i.swap_bytes()),
             DataValue::I64(i) => DataValue::I64(i.swap_bytes()),
             DataValue::I128(i) => DataValue::I128(i.swap_bytes()),
+            DataValue::F16(f) => DataValue::F16(Ieee16::with_bits(f.bits().swap_bytes())),
             DataValue::F32(f) => DataValue::F32(Ieee32::with_bits(f.bits().swap_bytes())),
             DataValue::F64(f) => DataValue::F64(Ieee64::with_bits(f.bits().swap_bytes())),
+            DataValue::F128(f) => DataValue::F128(Ieee128::with_bits(f.bits().swap_bytes())),
             DataValue::V128(mut v) => {
                 v.reverse();
                 DataValue::V128(v)
@@ -135,8 +146,10 @@ impl DataValue {
             DataValue::I32(i) => dst[..4].copy_from_slice(&i.to_ne_bytes()[..]),
             DataValue::I64(i) => dst[..8].copy_from_slice(&i.to_ne_bytes()[..]),
             DataValue::I128(i) => dst[..16].copy_from_slice(&i.to_ne_bytes()[..]),
+            DataValue::F16(f) => dst[..2].copy_from_slice(&f.bits().to_ne_bytes()[..]),
             DataValue::F32(f) => dst[..4].copy_from_slice(&f.bits().to_ne_bytes()[..]),
             DataValue::F64(f) => dst[..8].copy_from_slice(&f.bits().to_ne_bytes()[..]),
+            DataValue::F128(f) => dst[..16].copy_from_slice(&f.bits().to_ne_bytes()[..]),
             DataValue::V128(v) => dst[..16].copy_from_slice(&v[..]),
             DataValue::V64(v) => dst[..8].copy_from_slice(&v[..]),
         };
@@ -172,12 +185,18 @@ impl DataValue {
             types::I32 => DataValue::I32(i32::from_ne_bytes(src[..4].try_into().unwrap())),
             types::I64 => DataValue::I64(i64::from_ne_bytes(src[..8].try_into().unwrap())),
             types::I128 => DataValue::I128(i128::from_ne_bytes(src[..16].try_into().unwrap())),
+            types::F16 => DataValue::F16(Ieee16::with_bits(u16::from_ne_bytes(
+                src[..2].try_into().unwrap(),
+            ))),
             types::F32 => DataValue::F32(Ieee32::with_bits(u32::from_ne_bytes(
                 src[..4].try_into().unwrap(),
             ))),
             types::F64 => DataValue::F64(Ieee64::with_bits(u64::from_ne_bytes(
                 src[..8].try_into().unwrap(),
             ))),
+            types::F128 => DataValue::F128(Ieee128::with_bits(u128::from_ne_bytes(
+                src[..16].try_into().unwrap(),
+            ))),
             _ if ty.is_vector() => {
                 if ty.bytes() == 16 {
                     DataValue::V128(src[..16].try_into().unwrap())
@@ -233,8 +252,10 @@ impl DataValue {
             // We need to bit compare the floats to ensure that we produce the correct values
             // on NaN's. The test suite expects to assert the precise bit pattern on NaN's or
             // works around it in the tests themselves.
+            (DataValue::F16(a), DataValue::F16(b)) => a.bits() == b.bits(),
             (DataValue::F32(a), DataValue::F32(b)) => a.bits() == b.bits(),
             (DataValue::F64(a), DataValue::F64(b)) => a.bits() == b.bits(),
+            (DataValue::F128(a), DataValue::F128(b)) => a.bits() == b.bits(),
 
             // We don't need to worry about F32x4 / F64x2 Since we compare V128 which is already the
             // raw bytes anyway
@@ -305,8 +326,10 @@ build_conversion_impl!(i16, I16, I16);
 build_conversion_impl!(i32, I32, I32);
 build_conversion_impl!(i64, I64, I64);
 build_conversion_impl!(i128, I128, I128);
+build_conversion_impl!(Ieee16, F16, F16);
 build_conversion_impl!(Ieee32, F32, F32);
 build_conversion_impl!(Ieee64, F64, F64);
+build_conversion_impl!(Ieee128, F128, F128);
 build_conversion_impl!([u8; 16], V128, I8X16);
 build_conversion_impl!([u8; 8], V64, I8X8);
 impl From<Offset32> for DataValue {
@@ -324,8 +347,10 @@ impl Display for DataValue {
             DataValue::I64(dv) => write!(f, "{}", dv),
             DataValue::I128(dv) => write!(f, "{}", dv),
             // The Ieee* wrappers here print the expected syntax.
+            DataValue::F16(dv) => write!(f, "{}", dv),
             DataValue::F32(dv) => write!(f, "{}", dv),
             DataValue::F64(dv) => write!(f, "{}", dv),
+            DataValue::F128(dv) => write!(f, "{}", dv),
             // Again, for syntax consistency, use ConstantData, which in this case displays as hex.
             DataValue::V128(dv) => write!(f, "{}", ConstantData::from(&dv[..])),
             DataValue::V64(dv) => write!(f, "{}", ConstantData::from(&dv[..])),
diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs
index 153f331b9..c5f5d7bc6 100644
--- a/cranelift/codegen/src/ir/immediates.rs
+++ b/cranelift/codegen/src/ir/immediates.rs
@@ -468,6 +468,21 @@ impl FromStr for Offset32 {
     }
 }
 
+/// An IEEE binary16 immediate floating point value, represented as a u16
+/// containing the bit pattern.
+///
+/// We specifically avoid using a f16 here since some architectures may silently alter floats.
+/// See: <https://github.com/bytecodealliance/wasmtime/pull/2251#discussion_r498508646>
+///
+/// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but
+/// [PartialOrd] respects IEEE754 semantics.
+///
+/// All bit patterns are allowed.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
+#[repr(C)]
+pub struct Ieee16(u16);
+
 /// An IEEE binary32 immediate floating point value, represented as a u32
 /// containing the bit pattern.
 ///
@@ -498,6 +513,21 @@ pub struct Ieee32(u32);
 #[repr(C)]
 pub struct Ieee64(u64);
 
+/// An IEEE binary128 immediate floating point value, represented as a u128
+/// containing the bit pattern.
+///
+/// We specifically avoid using a f128 here since some architectures may silently alter floats.
+/// See: <https://github.com/bytecodealliance/wasmtime/pull/2251#discussion_r498508646>
+///
+/// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but
+/// [PartialOrd] respects IEEE754 semantics.
+///
+/// All bit patterns are allowed.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
+#[repr(C)]
+pub struct Ieee128(u128);
+
 /// Format a floating point number in a way that is reasonably human-readable, and that can be
 /// converted back to binary without any rounding issues. The hexadecimal formatting of normal and
 /// subnormal numbers is compatible with C99 and the `printf "%a"` format specifier. The NaN and Inf
@@ -508,13 +538,13 @@ pub struct Ieee64(u64);
 /// w - exponent field width in bits
 /// t - trailing significand field width in bits
 ///
-fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result {
+fn format_float(bits: u128, w: u8, t: u8, f: &mut Formatter) -> fmt::Result {
     debug_assert!(w > 0 && w <= 16, "Invalid exponent range");
-    debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64");
+    debug_assert!(1 + w + t <= 128, "Too large IEEE format for u128");
     debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size");
 
-    let max_e_bits = (1u64 << w) - 1;
-    let t_bits = bits & ((1u64 << t) - 1); // Trailing significand.
+    let max_e_bits = (1u128 << w) - 1;
+    let t_bits = bits & ((1u128 << t) - 1); // Trailing significand.
     let e_bits = (bits >> t) & max_e_bits; // Biased exponent.
     let sign_bit = (bits >> (w + t)) & 1;
 
@@ -583,13 +613,13 @@ fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result {
 /// w - exponent field width in bits
 /// t - trailing significand field width in bits
 ///
-fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
+fn parse_float(s: &str, w: u8, t: u8) -> Result<u128, &'static str> {
     debug_assert!(w > 0 && w <= 16, "Invalid exponent range");
-    debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64");
+    debug_assert!(1 + w + t <= 128, "Too large IEEE format for u128");
     debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size");
 
     let (sign_bit, s2) = if s.starts_with('-') {
-        (1u64 << (t + w), &s[1..])
+        (1u128 << (t + w), &s[1..])
     } else if s.starts_with('+') {
         (0, &s[1..])
     } else {
@@ -597,8 +627,8 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
     };
 
     if !s2.starts_with("0x") {
-        let max_e_bits = ((1u64 << w) - 1) << t;
-        let quiet_bit = 1u64 << (t - 1);
+        let max_e_bits = ((1u128 << w) - 1) << t;
+        let quiet_bit = 1u128 << (t - 1);
 
         // The only decimal encoding allowed is 0.
         if s2 == "0.0" {
@@ -615,7 +645,7 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
         }
         if s2.starts_with("NaN:0x") {
             // Quiet NaN with payload.
-            return match u64::from_str_radix(&s2[6..], 16) {
+            return match u128::from_str_radix(&s2[6..], 16) {
                 Ok(payload) if payload < quiet_bit => {
                     Ok(sign_bit | max_e_bits | quiet_bit | payload)
                 }
@@ -624,7 +654,7 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
         }
         if s2.starts_with("sNaN:0x") {
             // Signaling NaN with payload.
-            return match u64::from_str_radix(&s2[7..], 16) {
+            return match u128::from_str_radix(&s2[7..], 16) {
                 Ok(payload) if 0 < payload && payload < quiet_bit => {
                     Ok(sign_bit | max_e_bits | payload)
                 }
@@ -638,7 +668,7 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
 
     let mut digits = 0u8;
     let mut digits_before_period: Option<u8> = None;
-    let mut significand = 0u64;
+    let mut significand = 0u128;
     let mut exponent = 0i32;
 
     for (idx, ch) in s3.char_indices() {
@@ -665,10 +695,10 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
             _ => match ch.to_digit(16) {
                 Some(digit) => {
                     digits += 1;
-                    if digits > 16 {
+                    if digits > 32 {
                         return Err("Too many digits");
                     }
-                    significand = (significand << 4) | u64::from(digit);
+                    significand = (significand << 4) | u128::from(digit);
                 }
                 None => return Err("Invalid character"),
             },
@@ -691,10 +721,10 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
     };
 
     // Normalize the significand and exponent.
-    let significant_bits = (64 - significand.leading_zeros()) as u8;
+    let significant_bits = (128 - significand.leading_zeros()) as u8;
     if significant_bits > t + 1 {
         let adjust = significant_bits - (t + 1);
-        if significand & ((1u64 << adjust) - 1) != 0 {
+        if significand & ((1u128 << adjust) - 1) != 0 {
             return Err("Too many significant bits");
         }
         // Adjust significand down.
@@ -718,13 +748,13 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
         Err("Magnitude too large")
     } else if exponent > 0 {
         // This is a normal number.
-        let e_bits = (exponent as u64) << t;
+        let e_bits = (exponent as u128) << t;
         Ok(sign_bit | e_bits | t_bits)
     } else if 1 - exponent <= i32::from(t) {
         // This is a subnormal number: e = 0, t = significand bits.
         // Renormalize significand for exponent = 1.
         let adjust = 1 - exponent;
-        if significand & ((1u64 << adjust) - 1) != 0 {
+        if significand & ((1u128 << adjust) - 1) != 0 {
             Err("Subnormal underflow")
         } else {
             significand >>= adjust;
@@ -735,6 +765,84 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
     }
 }
 
+impl Ieee16 {
+    const SIGNIFICAND_BITS: u8 = 10;
+    const EXPONENT_BITS: u8 = 5;
+
+    /// Create a new `Ieee16` containing the bits of `x`.
+    pub fn with_bits(x: u16) -> Self {
+        Self(x)
+    }
+
+    /// Get the bitwise representation.
+    pub fn bits(self) -> u16 {
+        self.0
+    }
+
+    /// Returns true if self is positive or negative zero
+    pub fn is_zero(&self) -> bool {
+        self.partial_cmp(&Self::with_bits(0)) == Some(Ordering::Equal)
+    }
+}
+
+impl PartialOrd for Ieee16 {
+    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+        // FIXME(#8312): Use Rust `f16` comparisons once `f16` support is stabalised.
+        let significand_mask = u16::MAX >> (Self::EXPONENT_BITS + 1);
+        let sign_mask = 1 << (Self::EXPONENT_BITS + Self::SIGNIFICAND_BITS);
+        let exponent_mask = !sign_mask & !significand_mask;
+
+        let lhs_abs = self.bits() & !sign_mask;
+        let rhs_abs = rhs.bits() & !sign_mask;
+        if (lhs_abs & exponent_mask == exponent_mask && lhs_abs & significand_mask != 0)
+            && (rhs_abs & exponent_mask == exponent_mask && rhs_abs & significand_mask != 0)
+        {
+            // One of the floats is a NaN.
+            return None;
+        }
+        if lhs_abs == 0 && rhs_abs == 0 {
+            // Zeros are always equal regardless of sign.
+            return Some(Ordering::Equal);
+        }
+        let lhs_positive = self.bits() & sign_mask == 0;
+        let rhs_positive = rhs.bits() & sign_mask == 0;
+        if lhs_positive != rhs_positive {
+            // Different signs: negative < positive
+            return lhs_positive.partial_cmp(&rhs_positive);
+        }
+        // Finite or infinity will order correctly with an integer comparison of the bits.
+        if lhs_positive {
+            lhs_abs.partial_cmp(&rhs_abs)
+        } else {
+            // Reverse the comparison when both floats are negative.
+            rhs_abs.partial_cmp(&lhs_abs)
+        }
+    }
+}
+
+impl Display for Ieee16 {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        let bits: u16 = self.0;
+        format_float(
+            u128::from(bits),
+            Self::EXPONENT_BITS,
+            Self::SIGNIFICAND_BITS,
+            f,
+        )
+    }
+}
+
+impl FromStr for Ieee16 {
+    type Err = &'static str;
+
+    fn from_str(s: &str) -> Result<Self, &'static str> {
+        match parse_float(s, Self::EXPONENT_BITS, Self::SIGNIFICAND_BITS) {
+            Ok(b) => Ok(Self(b as u16)),
+            Err(s) => Err(s),
+        }
+    }
+}
+
 impl Ieee32 {
     /// Create a new `Ieee32` containing the bits of `x`.
     pub fn with_bits(x: u32) -> Self {
@@ -853,7 +961,7 @@ impl PartialOrd for Ieee32 {
 impl Display for Ieee32 {
     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
         let bits: u32 = self.0;
-        format_float(u64::from(bits), 8, 23, f)
+        format_float(u128::from(bits), 8, 23, f)
     }
 }
 
@@ -1071,7 +1179,7 @@ impl PartialOrd for Ieee64 {
 impl Display for Ieee64 {
     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
         let bits: u64 = self.0;
-        format_float(bits, 11, 52, f)
+        format_float(u128::from(bits), 11, 52, f)
     }
 }
 
@@ -1080,7 +1188,7 @@ impl FromStr for Ieee64 {
 
     fn from_str(s: &str) -> Result<Self, &'static str> {
         match parse_float(s, 11, 52) {
-            Ok(b) => Ok(Self(b)),
+            Ok(b) => Ok(Self(b as u64)),
             Err(s) => Err(s),
         }
     }
@@ -1176,6 +1284,79 @@ impl Not for Ieee64 {
     }
 }
 
+impl Ieee128 {
+    const SIGNIFICAND_BITS: u8 = 112;
+    const EXPONENT_BITS: u8 = 15;
+
+    /// Create a new `Ieee128` containing the bits of `x`.
+    pub fn with_bits(x: u128) -> Self {
+        Self(x)
+    }
+
+    /// Get the bitwise representation.
+    pub fn bits(self) -> u128 {
+        self.0
+    }
+
+    /// Returns true if self is positive or negative zero
+    pub fn is_zero(&self) -> bool {
+        self.partial_cmp(&Self::with_bits(0)) == Some(Ordering::Equal)
+    }
+}
+
+impl PartialOrd for Ieee128 {
+    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+        // FIXME(#8312): Use Rust `f128` comparisons once `f128` support is stabalised.
+        let significand_mask = u128::MAX >> (Self::EXPONENT_BITS + 1);
+        let sign_mask = 1 << (Self::EXPONENT_BITS + Self::SIGNIFICAND_BITS);
+        let exponent_mask = !sign_mask & !significand_mask;
+
+        let lhs_abs = self.bits() & !sign_mask;
+        let rhs_abs = rhs.bits() & !sign_mask;
+        if (lhs_abs & exponent_mask == exponent_mask && lhs_abs & significand_mask != 0)
+            && (rhs_abs & exponent_mask == exponent_mask && rhs_abs & significand_mask != 0)
+        {
+            // One of the floats is a NaN.
+            return None;
+        }
+        if lhs_abs == 0 && rhs_abs == 0 {
+            // Zeros are always equal regardless of sign.
+            return Some(Ordering::Equal);
+        }
+        let lhs_positive = self.bits() & sign_mask == 0;
+        let rhs_positive = rhs.bits() & sign_mask == 0;
+        if lhs_positive != rhs_positive {
+            // Different signs: negative < positive
+            return lhs_positive.partial_cmp(&rhs_positive);
+        }
+        // Finite or infinity will order correctly with an integer comparison of the bits.
+        if lhs_positive {
+            lhs_abs.partial_cmp(&rhs_abs)
+        } else {
+            // Reverse the comparison when both floats are negative.
+            rhs_abs.partial_cmp(&lhs_abs)
+        }
+    }
+}
+
+impl Display for Ieee128 {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        let bits: u128 = self.0;
+        format_float(bits, Self::EXPONENT_BITS, Self::SIGNIFICAND_BITS, f)
+    }
+}
+
+impl FromStr for Ieee128 {
+    type Err = &'static str;
+
+    fn from_str(s: &str) -> Result<Self, &'static str> {
+        match parse_float(s, Self::EXPONENT_BITS, Self::SIGNIFICAND_BITS) {
+            Ok(b) => Ok(Self(b)),
+            Err(s) => Err(s),
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -1346,6 +1527,134 @@ mod tests {
         parse_err::<Offset32>("+0x8000_0000", "Offset out of range");
     }
 
+    #[test]
+    fn format_ieee16() {
+        assert_eq!(Ieee16::with_bits(0).to_string(), "0.0"); // 0.0
+        assert_eq!(Ieee16::with_bits(0x8000).to_string(), "-0.0"); // -0.0
+        assert_eq!(Ieee16::with_bits(0x3c00).to_string(), "0x1.000p0"); // 1.0
+        assert_eq!(Ieee16::with_bits(0x3e00).to_string(), "0x1.800p0"); // 1.5
+        assert_eq!(Ieee16::with_bits(0x3800).to_string(), "0x1.000p-1"); // 0.5
+        assert_eq!(
+            Ieee16::with_bits(0x1400).to_string(), // `f16::EPSILON`
+            "0x1.000p-10"
+        );
+        assert_eq!(
+            Ieee16::with_bits(0xfbff).to_string(), // `f16::MIN`
+            "-0x1.ffcp15"
+        );
+        assert_eq!(
+            Ieee16::with_bits(0x7bff).to_string(), // `f16::MAX`
+            "0x1.ffcp15"
+        );
+        // Smallest positive normal number.
+        assert_eq!(
+            Ieee16::with_bits(0x0400).to_string(), // `f16::MIN_POSITIVE`
+            "0x1.000p-14"
+        );
+        // Subnormals.
+        assert_eq!(
+            Ieee16::with_bits(0x0200).to_string(), // `f16::MIN_POSITIVE / 2.0`
+            "0x0.800p-14"
+        );
+        assert_eq!(
+            Ieee16::with_bits(0x0001).to_string(), // `f16::MIN_POSITIVE * f16::EPSILON`
+            "0x0.004p-14"
+        );
+        assert_eq!(
+            Ieee16::with_bits(0x7c00).to_string(), // `f16::INFINITY`
+            "+Inf"
+        );
+        assert_eq!(
+            Ieee16::with_bits(0xfc00).to_string(), // `f16::NEG_INFINITY`
+            "-Inf"
+        );
+        assert_eq!(
+            Ieee16::with_bits(0x7e00).to_string(), // `f16::NAN`
+            "+NaN"
+        );
+        assert_eq!(
+            Ieee16::with_bits(0xfe00).to_string(), // `-f16::NAN`
+            "-NaN"
+        );
+        // Construct some qNaNs with payloads.
+        assert_eq!(Ieee16(0x7e01).to_string(), "+NaN:0x1");
+        assert_eq!(Ieee16(0x7f01).to_string(), "+NaN:0x101");
+        // Signaling NaNs.
+        assert_eq!(Ieee16(0x7c01).to_string(), "+sNaN:0x1");
+        assert_eq!(Ieee16(0x7d01).to_string(), "+sNaN:0x101");
+    }
+
+    #[test]
+    fn parse_ieee16() {
+        parse_ok::<Ieee16>("0.0", "0.0");
+        parse_ok::<Ieee16>("+0.0", "0.0");
+        parse_ok::<Ieee16>("-0.0", "-0.0");
+        parse_ok::<Ieee16>("0x0", "0.0");
+        parse_ok::<Ieee16>("0x0.0", "0.0");
+        parse_ok::<Ieee16>("0x.0", "0.0");
+        parse_ok::<Ieee16>("0x0.", "0.0");
+        parse_ok::<Ieee16>("0x1", "0x1.000p0");
+        parse_ok::<Ieee16>("+0x1", "0x1.000p0");
+        parse_ok::<Ieee16>("-0x1", "-0x1.000p0");
+        parse_ok::<Ieee16>("0x10", "0x1.000p4");
+        parse_ok::<Ieee16>("0x10.0", "0x1.000p4");
+        parse_err::<Ieee16>("0.", "Float must be hexadecimal");
+        parse_err::<Ieee16>(".0", "Float must be hexadecimal");
+        parse_err::<Ieee16>("0", "Float must be hexadecimal");
+        parse_err::<Ieee16>("-0", "Float must be hexadecimal");
+        parse_err::<Ieee16>(".", "Float must be hexadecimal");
+        parse_err::<Ieee16>("", "Float must be hexadecimal");
+        parse_err::<Ieee16>("-", "Float must be hexadecimal");
+        parse_err::<Ieee16>("0x", "No digits");
+        parse_err::<Ieee16>("0x..", "Multiple radix points");
+
+        // Check significant bits.
+        parse_ok::<Ieee16>("0x0.ffe", "0x1.ffcp-1");
+        parse_ok::<Ieee16>("0x1.ffc", "0x1.ffcp0");
+        parse_ok::<Ieee16>("0x3.ff8", "0x1.ffcp1");
+        parse_ok::<Ieee16>("0x7.ff", "0x1.ffcp2");
+        parse_ok::<Ieee16>("0xf.fe", "0x1.ffcp3");
+        parse_err::<Ieee16>("0x1.ffe", "Too many significant bits");
+        parse_err::<Ieee16>("0x1.ffc00000000000000000000000000000", "Too many digits");
+
+        // Exponents.
+        parse_ok::<Ieee16>("0x1p3", "0x1.000p3");
+        parse_ok::<Ieee16>("0x1p-3", "0x1.000p-3");
+        parse_ok::<Ieee16>("0x1.0p3", "0x1.000p3");
+        parse_ok::<Ieee16>("0x2.0p3", "0x1.000p4");
+        parse_ok::<Ieee16>("0x1.0p15", "0x1.000p15");
+        parse_ok::<Ieee16>("0x1.0p-14", "0x1.000p-14");
+        parse_ok::<Ieee16>("0x0.1p-10", "0x1.000p-14");
+        parse_err::<Ieee16>("0x2.0p15", "Magnitude too large");
+
+        // Subnormals.
+        parse_ok::<Ieee16>("0x1.0p-15", "0x0.800p-14");
+        parse_ok::<Ieee16>("0x1.0p-24", "0x0.004p-14");
+        parse_ok::<Ieee16>("0x0.004p-14", "0x0.004p-14");
+        parse_err::<Ieee16>("0x0.102p-14", "Subnormal underflow");
+        parse_err::<Ieee16>("0x1.8p-24", "Subnormal underflow");
+        parse_err::<Ieee16>("0x1.0p-25", "Magnitude too small");
+
+        // NaNs and Infs.
+        parse_ok::<Ieee16>("Inf", "+Inf");
+        parse_ok::<Ieee16>("+Inf", "+Inf");
+        parse_ok::<Ieee16>("-Inf", "-Inf");
+        parse_ok::<Ieee16>("NaN", "+NaN");
+        parse_ok::<Ieee16>("+NaN", "+NaN");
+        parse_ok::<Ieee16>("-NaN", "-NaN");
+        parse_ok::<Ieee16>("NaN:0x0", "+NaN");
+        parse_err::<Ieee16>("NaN:", "Float must be hexadecimal");
+        parse_err::<Ieee16>("NaN:0", "Float must be hexadecimal");
+        parse_err::<Ieee16>("NaN:0x", "Invalid NaN payload");
+        parse_ok::<Ieee16>("NaN:0x001", "+NaN:0x1");
+        parse_ok::<Ieee16>("NaN:0x101", "+NaN:0x101");
+        parse_err::<Ieee16>("NaN:0x301", "Invalid NaN payload");
+        parse_ok::<Ieee16>("sNaN:0x1", "+sNaN:0x1");
+        parse_err::<Ieee16>("sNaN:0x0", "Invalid sNaN payload");
+        parse_ok::<Ieee16>("sNaN:0x101", "+sNaN:0x101");
+        parse_err::<Ieee16>("sNaN:0x301", "Invalid sNaN payload");
+    }
+
     #[test]
     fn format_ieee32() {
         assert_eq!(Ieee32::with_float(0.0).to_string(), "0.0");
@@ -1416,7 +1725,7 @@ mod tests {
         parse_ok::<Ieee32>("0x7.fffff8", "0x1.fffffep2");
         parse_ok::<Ieee32>("0xf.fffff0", "0x1.fffffep3");
         parse_err::<Ieee32>("0x1.ffffff", "Too many significant bits");
-        parse_err::<Ieee32>("0x1.fffffe0000000000", "Too many digits");
+        parse_err::<Ieee32>("0x1.fffffe00000000000000000000000000", "Too many digits");
 
         // Exponents.
         parse_ok::<Ieee32>("0x1p3", "0x1.000000p3");
@@ -1556,7 +1865,7 @@ mod tests {
         parse_ok::<Ieee64>("0x7.ffffffffffffc", "0x1.fffffffffffffp2");
         parse_ok::<Ieee64>("0xf.ffffffffffff8", "0x1.fffffffffffffp3");
         parse_err::<Ieee64>("0x3.fffffffffffff", "Too many significant bits");
-        parse_err::<Ieee64>("0x001.fffffe00000000", "Too many digits");
+        parse_err::<Ieee64>("0x001.fffffe000000000000000000000000", "Too many digits");
 
         // Exponents.
         parse_ok::<Ieee64>("0x1p3", "0x1.0000000000000p3");
@@ -1613,4 +1922,188 @@ mod tests {
             });
         }
     }
+
+    #[test]
+    fn format_ieee128() {
+        assert_eq!(
+            Ieee128::with_bits(0x00000000000000000000000000000000).to_string(), // 0.0
+            "0.0"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x80000000000000000000000000000000).to_string(), // -0.0
+            "-0.0"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x3fff0000000000000000000000000000).to_string(), // 1.0
+            "0x1.0000000000000000000000000000p0"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x3fff8000000000000000000000000000).to_string(), // 1.5
+            "0x1.8000000000000000000000000000p0"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x3ffe0000000000000000000000000000).to_string(), // 0.5
+            "0x1.0000000000000000000000000000p-1"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x3f8f0000000000000000000000000000).to_string(), // `f128::EPSILON`
+            "0x1.0000000000000000000000000000p-112"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0xfffeffffffffffffffffffffffffffff).to_string(), // `f128::MIN`
+            "-0x1.ffffffffffffffffffffffffffffp16383"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x7ffeffffffffffffffffffffffffffff).to_string(), // `f128::MAX`
+            "0x1.ffffffffffffffffffffffffffffp16383"
+        );
+        // Smallest positive normal number.
+        assert_eq!(
+            Ieee128::with_bits(0x00010000000000000000000000000000).to_string(), // `f128::MIN_POSITIVE`
+            "0x1.0000000000000000000000000000p-16382"
+        );
+        // Subnormals.
+        assert_eq!(
+            Ieee128::with_bits(0x00008000000000000000000000000000).to_string(), // `f128::MIN_POSITIVE / 2.0`
+            "0x0.8000000000000000000000000000p-16382"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x00000000000000000000000000000001).to_string(), // `f128::MIN_POSITIVE * f128::EPSILON`
+            "0x0.0000000000000000000000000001p-16382"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x7fff0000000000000000000000000000).to_string(), // `f128::INFINITY`
+            "+Inf"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0xffff0000000000000000000000000000).to_string(), // `f128::NEG_INFINITY`
+            "-Inf"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0x7fff8000000000000000000000000000).to_string(), // `f128::NAN`
+            "+NaN"
+        );
+        assert_eq!(
+            Ieee128::with_bits(0xffff8000000000000000000000000000).to_string(), // `-f128::NAN`
+            "-NaN"
+        );
+        // Construct some qNaNs with payloads.
+        assert_eq!(
+            Ieee128(0x7fff8000000000000000000000000001).to_string(),
+            "+NaN:0x1"
+        );
+        assert_eq!(
+            Ieee128(0x7fffc000000000000000000000000001).to_string(),
+            "+NaN:0x4000000000000000000000000001"
+        );
+        // Signaling NaNs.
+        assert_eq!(
+            Ieee128(0x7fff0000000000000000000000000001).to_string(),
+            "+sNaN:0x1"
+        );
+        assert_eq!(
+            Ieee128(0x7fff4000000000000000000000000001).to_string(),
+            "+sNaN:0x4000000000000000000000000001"
+        );
+    }
+
+    #[test]
+    fn parse_ieee128() {
+        parse_ok::<Ieee128>("0.0", "0.0");
+        parse_ok::<Ieee128>("-0.0", "-0.0");
+        parse_ok::<Ieee128>("0x0", "0.0");
+        parse_ok::<Ieee128>("0x0.0", "0.0");
+        parse_ok::<Ieee128>("0x.0", "0.0");
+        parse_ok::<Ieee128>("0x0.", "0.0");
+        parse_ok::<Ieee128>("0x1", "0x1.0000000000000000000000000000p0");
+        parse_ok::<Ieee128>("-0x1", "-0x1.0000000000000000000000000000p0");
+        parse_ok::<Ieee128>("0x10", "0x1.0000000000000000000000000000p4");
+        parse_ok::<Ieee128>("0x10.0", "0x1.0000000000000000000000000000p4");
+        parse_err::<Ieee128>("0.", "Float must be hexadecimal");
+        parse_err::<Ieee128>(".0", "Float must be hexadecimal");
+        parse_err::<Ieee128>("0", "Float must be hexadecimal");
+        parse_err::<Ieee128>("-0", "Float must be hexadecimal");
+        parse_err::<Ieee128>(".", "Float must be hexadecimal");
+        parse_err::<Ieee128>("", "Float must be hexadecimal");
+        parse_err::<Ieee128>("-", "Float must be hexadecimal");
+        parse_err::<Ieee128>("0x", "No digits");
+        parse_err::<Ieee128>("0x..", "Multiple radix points");
+
+        // Check significant bits.
+        parse_ok::<Ieee128>(
+            "0x0.ffffffffffffffffffffffffffff8",
+            "0x1.ffffffffffffffffffffffffffffp-1",
+        );
+        parse_ok::<Ieee128>(
+            "0x1.ffffffffffffffffffffffffffff",
+            "0x1.ffffffffffffffffffffffffffffp0",
+        );
+        parse_ok::<Ieee128>(
+            "0x3.fffffffffffffffffffffffffffe",
+            "0x1.ffffffffffffffffffffffffffffp1",
+        );
+        parse_ok::<Ieee128>(
+            "0x7.fffffffffffffffffffffffffffc",
+            "0x1.ffffffffffffffffffffffffffffp2",
+        );
+        parse_ok::<Ieee128>(
+            "0xf.fffffffffffffffffffffffffff8",
+            "0x1.ffffffffffffffffffffffffffffp3",
+        );
+        parse_err::<Ieee128>(
+            "0x3.ffffffffffffffffffffffffffff",
+            "Too many significant bits",
+        );
+        parse_err::<Ieee128>("0x001.fffffe000000000000000000000000", "Too many digits");
+
+        // Exponents.
+        parse_ok::<Ieee128>("0x1p3", "0x1.0000000000000000000000000000p3");
+        parse_ok::<Ieee128>("0x1p-3", "0x1.0000000000000000000000000000p-3");
+        parse_ok::<Ieee128>("0x1.0p3", "0x1.0000000000000000000000000000p3");
+        parse_ok::<Ieee128>("0x2.0p3", "0x1.0000000000000000000000000000p4");
+        parse_ok::<Ieee128>("0x1.0p16383", "0x1.0000000000000000000000000000p16383");
+        parse_ok::<Ieee128>("0x1.0p-16382", "0x1.0000000000000000000000000000p-16382");
+        parse_ok::<Ieee128>("0x0.1p-16378", "0x1.0000000000000000000000000000p-16382");
+        parse_err::<Ieee128>("0x2.0p16383", "Magnitude too large");
+
+        // Subnormals.
+        parse_ok::<Ieee128>("0x1.0p-16383", "0x0.8000000000000000000000000000p-16382");
+        parse_ok::<Ieee128>("0x1.0p-16494", "0x0.0000000000000000000000000001p-16382");
+        parse_ok::<Ieee128>(
+            "0x0.0000000000000000000000000001p-16382",
+            "0x0.0000000000000000000000000001p-16382",
+        );
+        parse_err::<Ieee128>(
+            "0x0.10000000000000000000000000008p-16382",
+            "Subnormal underflow",
+        );
+        parse_err::<Ieee128>("0x1.8p-16494", "Subnormal underflow");
+        parse_err::<Ieee128>("0x1.0p-16495", "Magnitude too small");
+
+        // NaNs and Infs.
+        parse_ok::<Ieee128>("Inf", "+Inf");
+        parse_ok::<Ieee128>("-Inf", "-Inf");
+        parse_ok::<Ieee128>("NaN", "+NaN");
+        parse_ok::<Ieee128>("-NaN", "-NaN");
+        parse_ok::<Ieee128>("NaN:0x0", "+NaN");
+        parse_err::<Ieee128>("NaN:", "Float must be hexadecimal");
+        parse_err::<Ieee128>("NaN:0", "Float must be hexadecimal");
+        parse_err::<Ieee128>("NaN:0x", "Invalid NaN payload");
+        parse_ok::<Ieee128>("NaN:0x000001", "+NaN:0x1");
+        parse_ok::<Ieee128>(
+            "NaN:0x4000000000000000000000000001",
+            "+NaN:0x4000000000000000000000000001",
+        );
+        parse_err::<Ieee128>("NaN:0x8000000000000000000000000001", "Invalid NaN payload");
+        parse_ok::<Ieee128>("sNaN:0x1", "+sNaN:0x1");
+        parse_err::<Ieee128>("sNaN:0x0", "Invalid sNaN payload");
+        parse_ok::<Ieee128>(
+            "sNaN:0x4000000000000000000000000001",
+            "+sNaN:0x4000000000000000000000000001",
+        );
+        parse_err::<Ieee128>(
+            "sNaN:0x8000000000000000000000000001",
+            "Invalid sNaN payload",
+        );
+    }
 }
diff --git a/cranelift/codegen/src/ir/instructions.rs b/cranelift/codegen/src/ir/instructions.rs
index fd5208070..6d483aba3 100644
--- a/cranelift/codegen/src/ir/instructions.rs
+++ b/cranelift/codegen/src/ir/instructions.rs
@@ -794,8 +794,8 @@ impl OperandConstraint {
                     tys.ints = BitSet8::from_range(3, ctrl_type_bits as u8);
                 } else if ctrl_type.is_float() {
                     // The upper bound in from_range is exclusive, and we want to exclude the
-                    // control type to construct the interval of [F32, ctrl_type).
-                    tys.floats = BitSet8::from_range(5, ctrl_type_bits as u8);
+                    // control type to construct the interval of [F16, ctrl_type).
+                    tys.floats = BitSet8::from_range(4, ctrl_type_bits as u8);
                 } else {
                     panic!("The Narrower constraint only operates on floats or ints");
                 }
@@ -822,10 +822,11 @@ impl OperandConstraint {
                         tys.ints = BitSet8::from_range(lower_bound, 8);
                     }
                 } else if ctrl_type.is_float() {
-                    // The interval should include all float types wider than `ctrl_type`, so we
-                    // use `2^7` as the upper bound, and add one to the bits of `ctrl_type` to
-                    // define the interval `(ctrl_type, F64]`.
-                    tys.floats = BitSet8::from_range(ctrl_type_bits as u8 + 1, 7);
+                    // Same as above but for `tys.floats`, as the largest float type is F128.
+                    let lower_bound = ctrl_type_bits as u8 + 1;
+                    if lower_bound < BitSet8::capacity() {
+                        tys.floats = BitSet8::from_range(lower_bound, 8);
+                    }
                 } else {
                     panic!("The Wider constraint only operates on floats or ints");
                 }
@@ -964,7 +965,9 @@ mod tests {
         assert!(vts.contains(I64));
         assert!(vts.contains(I32X4));
         assert!(vts.contains(I32X4XN));
+        assert!(!vts.contains(F16));
         assert!(!vts.contains(F32));
+        assert!(!vts.contains(F128));
         assert!(vts.contains(R32));
         assert!(vts.contains(R64));
         assert_eq!(vts.example().to_string(), "i32");
diff --git a/cranelift/codegen/src/ir/types.rs b/cranelift/codegen/src/ir/types.rs
index 928a28526..a62fffa61 100644
--- a/cranelift/codegen/src/ir/types.rs
+++ b/cranelift/codegen/src/ir/types.rs
@@ -14,7 +14,7 @@ use target_lexicon::{PointerWidth, Triple};
 ///
 /// Basic integer types: `I8`, `I16`, `I32`, `I64`, and `I128`. These types are sign-agnostic.
 ///
-/// Basic floating point types: `F32` and `F64`. IEEE single and double precision.
+/// Basic floating point types: `F16`, `F32`, `F64`, and `F128`. IEEE half, single, double, and quadruple precision.
 ///
 /// SIMD vector types have power-of-two lanes, up to 256. Lanes can be any int/float type.
 ///
@@ -56,10 +56,10 @@ impl Type {
     pub fn log2_lane_bits(self) -> u32 {
         match self.lane_type() {
             I8 => 3,
-            I16 => 4,
+            I16 | F16 => 4,
             I32 | F32 | R32 => 5,
             I64 | F64 | R64 => 6,
-            I128 => 7,
+            I128 | F128 => 7,
             _ => 0,
         }
     }
@@ -68,10 +68,10 @@ impl Type {
     pub fn lane_bits(self) -> u32 {
         match self.lane_type() {
             I8 => 8,
-            I16 => 16,
+            I16 | F16 => 16,
             I32 | F32 | R32 => 32,
             I64 | F64 | R64 => 64,
-            I128 => 128,
+            I128 | F128 => 128,
             _ => 0,
         }
     }
@@ -136,11 +136,11 @@ impl Type {
         // Replace the low 4 bits with the boolean version, preserve the high 4 bits.
         self.replace_lanes(match self.lane_type() {
             I8 => I8,
-            I16 => I16,
+            I16 | F16 => I16,
             I32 | F32 => I32,
             I64 | F64 => I64,
             R32 | R64 => panic!("Reference types are not truthy"),
-            I128 => I128,
+            I128 | F128 => I128,
             _ => I8,
         })
     }
@@ -161,10 +161,10 @@ impl Type {
     pub fn as_int(self) -> Self {
         self.replace_lanes(match self.lane_type() {
             I8 => I8,
-            I16 => I16,
+            I16 | F16 => I16,
             I32 | F32 | R32 => I32,
             I64 | F64 | R64 => I64,
-            I128 => I128,
+            I128 | F128 => I128,
             _ => unimplemented!(),
         })
     }
@@ -177,7 +177,9 @@ impl Type {
             I32 => I16,
             I64 => I32,
             I128 => I64,
+            F32 => F16,
             F64 => F32,
+            F128 => F64,
             _ => return None,
         }))
     }
@@ -190,7 +192,9 @@ impl Type {
             I16 => I32,
             I32 => I64,
             I64 => I128,
+            F16 => F32,
             F32 => F64,
+            F64 => F128,
             _ => return None,
         }))
     }
@@ -235,7 +239,7 @@ impl Type {
     /// Is this a scalar floating point type?
     pub fn is_float(self) -> bool {
         match self {
-            F32 | F64 => true,
+            F16 | F32 | F64 | F128 => true,
             _ => false,
         }
     }
@@ -486,7 +490,9 @@ mod tests {
         assert_eq!(I64, I64.lane_type());
         assert_eq!(I128, I128.lane_type());
         assert_eq!(F32, F32.lane_type());
+        assert_eq!(F16, F16.lane_type());
         assert_eq!(F64, F64.lane_type());
+        assert_eq!(F128, F128.lane_type());
         assert_eq!(I32, I32X4.lane_type());
         assert_eq!(F64, F64X2.lane_type());
         assert_eq!(R32, R32.lane_type());
@@ -498,8 +504,10 @@ mod tests {
         assert_eq!(I32.lane_bits(), 32);
         assert_eq!(I64.lane_bits(), 64);
         assert_eq!(I128.lane_bits(), 128);
+        assert_eq!(F16.lane_bits(), 16);
         assert_eq!(F32.lane_bits(), 32);
         assert_eq!(F64.lane_bits(), 64);
+        assert_eq!(F128.lane_bits(), 128);
         assert_eq!(R32.lane_bits(), 32);
         assert_eq!(R64.lane_bits(), 64);
     }
@@ -514,8 +522,10 @@ mod tests {
         assert_eq!(I32X4.half_width(), Some(I16X4));
         assert_eq!(I64.half_width(), Some(I32));
         assert_eq!(I128.half_width(), Some(I64));
-        assert_eq!(F32.half_width(), None);
+        assert_eq!(F16.half_width(), None);
+        assert_eq!(F32.half_width(), Some(F16));
         assert_eq!(F64.half_width(), Some(F32));
+        assert_eq!(F128.half_width(), Some(F64));
 
         assert_eq!(INVALID.double_width(), None);
         assert_eq!(I8.double_width(), Some(I16));
@@ -524,8 +534,10 @@ mod tests {
         assert_eq!(I32X4.double_width(), Some(I64X4));
         assert_eq!(I64.double_width(), Some(I128));
         assert_eq!(I128.double_width(), None);
+        assert_eq!(F16.double_width(), Some(F32));
         assert_eq!(F32.double_width(), Some(F64));
-        assert_eq!(F64.double_width(), None);
+        assert_eq!(F64.double_width(), Some(F128));
+        assert_eq!(F128.double_width(), None);
     }
 
     #[test]
@@ -558,14 +570,18 @@ mod tests {
         // Conversions to and from vectors.
         assert_eq!(I8.by(16).unwrap().vector_to_dynamic(), Some(I8X16XN));
         assert_eq!(I16.by(8).unwrap().vector_to_dynamic(), Some(I16X8XN));
+        assert_eq!(F16.by(8).unwrap().vector_to_dynamic(), Some(F16X8XN));
         assert_eq!(I32.by(4).unwrap().vector_to_dynamic(), Some(I32X4XN));
         assert_eq!(F32.by(4).unwrap().vector_to_dynamic(), Some(F32X4XN));
         assert_eq!(F64.by(2).unwrap().vector_to_dynamic(), Some(F64X2XN));
         assert_eq!(I128.by(2).unwrap().vector_to_dynamic(), Some(I128X2XN));
+        assert_eq!(F128.by(2).unwrap().vector_to_dynamic(), Some(F128X2XN));
 
         assert_eq!(I128X2XN.dynamic_to_vector(), Some(I128X2));
+        assert_eq!(F16X4XN.dynamic_to_vector(), Some(F16X4));
         assert_eq!(F32X4XN.dynamic_to_vector(), Some(F32X4));
         assert_eq!(F64X4XN.dynamic_to_vector(), Some(F64X4));
+        assert_eq!(F128X4XN.dynamic_to_vector(), Some(F128X4));
         assert_eq!(I32X2XN.dynamic_to_vector(), Some(I32X2));
         assert_eq!(I32X8XN.dynamic_to_vector(), Some(I32X8));
         assert_eq!(I16X16XN.dynamic_to_vector(), Some(I16X16));
diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs
index bb9703bfc..01d0377e4 100644
--- a/cranelift/interpreter/src/step.rs
+++ b/cranelift/interpreter/src/step.rs
@@ -1419,7 +1419,7 @@ pub(crate) fn extractlanes(
 
     let iterations = match lane_type {
         types::I8 => 1,
-        types::I16 => 2,
+        types::I16 | types::F16 => 2,
         types::I32 | types::F32 => 4,
         types::I64 | types::F64 => 8,
         _ => unimplemented!("vectors with lanes wider than 64-bits are currently unsupported."),
@@ -1458,7 +1458,7 @@ fn vectorizelanes_all(x: &[DataValue], vector_type: types::Type) -> ValueResult<
     let lane_type = vector_type.lane_type();
     let iterations = match lane_type {
         types::I8 => 1,
-        types::I16 => 2,
+        types::I16 | types::F16 => 2,
         types::I32 | types::F32 => 4,
         types::I64 | types::F64 => 8,
         _ => unimplemented!("vectors with lanes wider than 64-bits are currently unsupported."),
diff --git a/cranelift/interpreter/src/value.rs b/cranelift/interpreter/src/value.rs
index 4689fe6a1..d72ee5714 100644
--- a/cranelift/interpreter/src/value.rs
+++ b/cranelift/interpreter/src/value.rs
@@ -5,7 +5,7 @@
 
 use core::fmt::{self, Display, Formatter};
 use cranelift_codegen::data_value::{DataValue, DataValueCastFailure};
-use cranelift_codegen::ir::immediates::{Ieee32, Ieee64};
+use cranelift_codegen::ir::immediates::{Ieee128, Ieee16, Ieee32, Ieee64};
 use cranelift_codegen::ir::{types, Type};
 use thiserror::Error;
 
@@ -318,7 +318,7 @@ impl DataValueExt for DataValue {
 
     fn is_float(&self) -> bool {
         match self {
-            DataValue::F32(_) | DataValue::F64(_) => true,
+            DataValue::F16(_) | DataValue::F32(_) | DataValue::F64(_) | DataValue::F128(_) => true,
             _ => false,
         }
     }
@@ -399,10 +399,14 @@ impl DataValueExt for DataValue {
                 (val, ty) if val.ty().is_int() && ty.is_int() => {
                     DataValue::from_integer(val.into_int_signed()?, ty)?
                 }
+                (DataValue::I16(n), types::F16) => DataValue::F16(Ieee16::with_bits(n as u16)),
                 (DataValue::I32(n), types::F32) => DataValue::F32(f32::from_bits(n as u32).into()),
                 (DataValue::I64(n), types::F64) => DataValue::F64(f64::from_bits(n as u64).into()),
+                (DataValue::I128(n), types::F128) => DataValue::F128(Ieee128::with_bits(n as u128)),
+                (DataValue::F16(n), types::I16) => DataValue::I16(n.bits() as i16),
                 (DataValue::F32(n), types::I32) => DataValue::I32(n.bits() as i32),
                 (DataValue::F64(n), types::I64) => DataValue::I64(n.bits() as i64),
+                (DataValue::F128(n), types::I128) => DataValue::I128(n.bits() as i128),
                 (DataValue::F32(n), types::F64) => DataValue::F64((n.as_f32() as f64).into()),
                 (dv, t) if (t.is_int() || t.is_float()) && dv.ty() == t => dv,
                 (dv, _) => unimplemented!("conversion: {} -> {:?}", dv.ty(), kind),
@@ -502,8 +506,10 @@ impl DataValueExt for DataValue {
             DataValue::I32(f) => Ok(*f == 0),
             DataValue::I64(f) => Ok(*f == 0),
             DataValue::I128(f) => Ok(*f == 0),
+            DataValue::F16(f) => Ok(f.is_zero()),
             DataValue::F32(f) => Ok(f.is_zero()),
             DataValue::F64(f) => Ok(f.is_zero()),
+            DataValue::F128(f) => Ok(f.is_zero()),
             DataValue::V64(_) | DataValue::V128(_) => {
                 Err(ValueError::InvalidType(ValueTypeClass::Float, self.ty()))
             }
diff --git a/cranelift/reader/src/lexer.rs b/cranelift/reader/src/lexer.rs
index 0fec4fcd2..afdf3043b 100644
--- a/cranelift/reader/src/lexer.rs
+++ b/cranelift/reader/src/lexer.rs
@@ -369,8 +369,10 @@ impl<'a> Lexer<'a> {
             "i32" => types::I32,
             "i64" => types::I64,
             "i128" => types::I128,
+            "f16" => types::F16,
             "f32" => types::F32,
             "f64" => types::F64,
+            "f128" => types::F128,
             "r32" => types::R32,
             "r64" => types::R64,
             _ => return None,
@@ -626,7 +628,7 @@ mod tests {
     fn lex_identifiers() {
         let mut lex = Lexer::new(
             "v0 v00 vx01 block1234567890 block5234567890 v1x vx1 vxvx4 \
-             function0 function i8 i32x4 f32x5",
+             function0 function i8 i32x4 f32x5 f16 f128",
         );
         assert_eq!(
             lex.next(),
@@ -647,6 +649,8 @@ mod tests {
         assert_eq!(lex.next(), token(Token::Type(types::I8), 1));
         assert_eq!(lex.next(), token(Token::Type(types::I32X4), 1));
         assert_eq!(lex.next(), token(Token::Identifier("f32x5"), 1));
+        assert_eq!(lex.next(), token(Token::Type(types::F16), 1));
+        assert_eq!(lex.next(), token(Token::Type(types::F128), 1));
         assert_eq!(lex.next(), None);
     }
 
diff --git a/cranelift/reader/src/parser.rs b/cranelift/reader/src/parser.rs
index 6068af8de..3783bb49e 100644
--- a/cranelift/reader/src/parser.rs
+++ b/cranelift/reader/src/parser.rs
@@ -10,7 +10,9 @@ use crate::testfile::{Comment, Details, Feature, TestFile};
 use cranelift_codegen::data_value::DataValue;
 use cranelift_codegen::entity::{EntityRef, PrimaryMap};
 use cranelift_codegen::ir::entities::{AnyEntity, DynamicType, MemoryType};
-use cranelift_codegen::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64};
+use cranelift_codegen::ir::immediates::{
+    Ieee128, Ieee16, Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64,
+};
 use cranelift_codegen::ir::instructions::{InstructionData, InstructionFormat, VariableArgs};
 use cranelift_codegen::ir::pcc::{BaseExpr, Expr, Fact};
 use cranelift_codegen::ir::types;
@@ -855,6 +857,18 @@ impl<'a> Parser<'a> {
         Ok(Imm64::new(0))
     }
 
+    // Match and consume an Ieee16 immediate.
+    fn match_ieee16(&mut self, err_msg: &str) -> ParseResult<Ieee16> {
+        if let Some(Token::Float(text)) = self.token() {
+            self.consume();
+            // Lexer just gives us raw text that looks like a float.
+            // Parse it as an Ieee16 to check for the right number of digits and other issues.
+            text.parse().map_err(|e| self.error(e))
+        } else {
+            err!(self.loc, err_msg)
+        }
+    }
+
     // Match and consume an Ieee32 immediate.
     fn match_ieee32(&mut self, err_msg: &str) -> ParseResult<Ieee32> {
         if let Some(Token::Float(text)) = self.token() {
@@ -879,6 +893,18 @@ impl<'a> Parser<'a> {
         }
     }
 
+    // Match and consume an Ieee128 immediate.
+    fn match_ieee128(&mut self, err_msg: &str) -> ParseResult<Ieee128> {
+        if let Some(Token::Float(text)) = self.token() {
+            self.consume();
+            // Lexer just gives us raw text that looks like a float.
+            // Parse it as an Ieee128 to check for the right number of digits and other issues.
+            text.parse().map_err(|e| self.error(e))
+        } else {
+            err!(self.loc, err_msg)
+        }
+    }
+
     // Match and consume an enumerated immediate, like one of the condition codes.
     fn match_enum<T: FromStr>(&mut self, err_msg: &str) -> ParseResult<T> {
         if let Some(Token::Identifier(text)) = self.token() {
@@ -2793,8 +2819,10 @@ impl<'a> Parser<'a> {
             I32 => DataValue::from(self.match_imm32("expected an i32")?),
             I64 => DataValue::from(Into::<i64>::into(self.match_imm64("expected an i64")?)),
             I128 => DataValue::from(self.match_imm128("expected an i128")?),
+            F16 => DataValue::from(self.match_ieee16("expected an f16")?),
             F32 => DataValue::from(self.match_ieee32("expected an f32")?),
             F64 => DataValue::from(self.match_ieee64("expected an f64")?),
+            F128 => DataValue::from(self.match_ieee128("expected an f128")?),
             _ if (ty.is_vector() || ty.is_dynamic_vector()) => {
                 let as_vec = self.match_uimm128(ty)?.into_vec();
                 if as_vec.len() == 16 {
@@ -3254,12 +3282,13 @@ mod tests {
         assert_eq!(sig.returns.len(), 0);
         assert_eq!(sig.call_conv, CallConv::SystemV);
 
-        let sig2 = Parser::new("(i8 uext, f32, f64, i32 sret) -> i32 sext, f64 system_v")
-            .parse_signature()
-            .unwrap();
+        let sig2 =
+            Parser::new("(i8 uext, f16, f32, f64, f128, i32 sret) -> i32 sext, f64 system_v")
+                .parse_signature()
+                .unwrap();
         assert_eq!(
             sig2.to_string(),
-            "(i8 uext, f32, f64, i32 sret) -> i32 sext, f64 system_v"
+            "(i8 uext, f16, f32, f64, f128, i32 sret) -> i32 sext, f64 system_v"
         );
         assert_eq!(sig2.call_conv, CallConv::SystemV);
 
@@ -3865,8 +3894,13 @@ mod tests {
             "1512366032949150931280199141537564007"
         );
         assert_eq!(parse("1234567", I128).to_string(), "1234567");
+        assert_eq!(parse("0x16.1", F16).to_string(), "0x1.610p4");
         assert_eq!(parse("0x32.32", F32).to_string(), "0x1.919000p5");
         assert_eq!(parse("0x64.64", F64).to_string(), "0x1.9190000000000p6");
+        assert_eq!(
+            parse("0x128.128", F128).to_string(),
+            "0x1.2812800000000000000000000000p8"
+        );
         assert_eq!(
             parse("[0 1 2 3]", I32X4).to_string(),
             "0x00000003000000020000000100000000"


Test patch:
diff --git a/cranelift/filetests/filetests/runtests/bitcast-f16-f128.clif b/cranelift/filetests/filetests/runtests/bitcast-f16-f128.clif
new file mode 100644
index 000000000..5448ae481
--- /dev/null
+++ b/cranelift/filetests/filetests/runtests/bitcast-f16-f128.clif
@@ -0,0 +1,61 @@
+test interpret
+
+function %bitcast_i16_f16(i16) -> f16 fast {
+block0(v0: i16):
+    v1 = bitcast.f16 v0
+    return v1
+}
+; run: %bitcast_i16_f16(0x0000) == 0x0.0
+; run: %bitcast_i16_f16(0x0001) == 0x0.004p-14
+; run: %bitcast_i16_f16(0x7c00) == Inf
+; run: %bitcast_i16_f16(0xfe00) == -NaN
+; run: %bitcast_i16_f16(0x7c01) == +sNaN:0x1
+; run: %bitcast_i16_f16(0x3c00) == 0x1.0
+; run: %bitcast_i16_f16(0x47fe) == 0x7.fe
+; run: %bitcast_i16_f16(0xf0e0) == -0x1.380p13
+; run: %bitcast_i16_f16(0xabcd) == -0x1.f34p-5
+
+function %bitcast_i128_f128(i128) -> f128 fast {
+block0(v0: i128):
+    v1 = bitcast.f128 v0
+    return v1
+}
+; run: %bitcast_i128_f128(0x00000000000000000000000000000000) == 0x0.0
+; run: %bitcast_i128_f128(0x00000000000000000000000000000001) == 0x0.0000000000000000000000000001p-16382
+; run: %bitcast_i128_f128(0x7fff0000000000000000000000000000) == Inf
+; run: %bitcast_i128_f128(0xffff8000000000000000000000000000) == -NaN
+; run: %bitcast_i128_f128(0x7fff0000000000000000000000000001) == +sNaN:0x1
+; run: %bitcast_i128_f128(0x3fff0000000000000000000000000000) == 0x1.0
+; run: %bitcast_i128_f128(0x3fff7fef123456789abcdefcda456987) == 0x1.7fef123456789abcdefcda456987
+; run: %bitcast_i128_f128(0xf0e0d0c0b0a090807060504030201000) == -0x1.d0c0b0a090807060504030201000p12513
+; run: %bitcast_i128_f128(0xabcdef01234567890123456789abcdef) == -0x1.ef01234567890123456789abcdefp-5170
+
+function %bitcast_f16_i16(f16) -> i16 fast {
+block0(v0: f16):
+    v1 = bitcast.i16 v0
+    return v1
+}
+; run: %bitcast_f16_i16(0x0.0) == 0x0000
+; run: %bitcast_f16_i16(0x0.004p-14) == 0x0001
+; run: %bitcast_f16_i16(Inf) == 0x7c00
+; run: %bitcast_f16_i16(-NaN) == 0xfe00
+; run: %bitcast_f16_i16(+sNaN:0x1) == 0x7c01
+; run: %bitcast_f16_i16(0x1.0) == 0x3c00
+; run: %bitcast_f16_i16(0x7.fe) == 0x47fe
+; run: %bitcast_f16_i16(-0x1.3c0p13) == 0xf0f0
+; run: %bitcast_f16_i16(-0x1.f34p-5) == 0xabcd
+
+function %bitcast_f128_i128(f128) -> i128 fast {
+block0(v0: f128):
+    v1 = bitcast.i128 v0
+    return v1
+}
+; run: %bitcast_f128_i128(0x0.0) == 0x00000000000000000000000000000000
+; run: %bitcast_f128_i128(0x0.0000000000000000000000000001p-16382) == 0x00000000000000000000000000000001
+; run: %bitcast_f128_i128(Inf) == 0x7fff0000000000000000000000000000
+; run: %bitcast_f128_i128(-NaN) == 0xffff8000000000000000000000000000
+; run: %bitcast_f128_i128(+sNaN:0x1) == 0x7fff0000000000000000000000000001
+; run: %bitcast_f128_i128(0x1.0) == 0x3fff0000000000000000000000000000
+; run: %bitcast_f128_i128(0x1.7fef123456789abcdefcda456987) == 0x3fff7fef123456789abcdefcda456987
+; run: %bitcast_f128_i128(-0x1.d0c0b0a090807060504030201000p12513) == 0xf0e0d0c0b0a090807060504030201000
+; run: %bitcast_f128_i128(-0x1.ef01234567890123456789abcdefp-5170) == 0xabcdef01234567890123456789abcdef