Repository: bytecodealliance/wasmtime
Pull request #9013 (2024-07-25 22:54:59)

CI: Test Pulley on 32-bit and `no_std` targets
This PR adds CI testing for Pulley on the following 32-bit targets:

* `i686-unknown-linux-gnu`
* `armv7-unknown-linux-gnueabihf`

It also checks that Pulley builds for `no_std` targets.

Our existing test sharding means that it is already tested on a big-endian
platform (`s390x-unknown-linux-gnu`) as well as on different OSes (Linux,
Windows, and macOS).

So altogether we are now testing a pretty wide range of portability dimensions
for Pulley in CI:

* A variety of different architectures, including 32- vs. 64-bit pointer widths
* `std` vs. `no_std`
* Little- vs. big-endian
* Linux vs. Windows vs. macOS

I think our CI coverage for Pulley is in a pretty good place now!


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 14ed47a85..7ff86f17b 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -433,6 +433,7 @@ jobs:
     - run: rustup target add x86_64-unknown-none
     - run: cargo check --target x86_64-unknown-none -p wasmtime --no-default-features --features runtime,gc,component-model
     - run: cargo check --target x86_64-unknown-none -p cranelift-control --no-default-features
+    - run: cargo check --target x86_64-unknown-none -p pulley-interpreter --features encode,decode,disas,interp
 
     # Check that wasmtime compiles with panic=abort since there's some `#[cfg]`
     # for specifically panic=abort there.
@@ -604,22 +605,30 @@ jobs:
 
     - run: cargo fetch --locked
 
-    - uses: actions/cache@v4
-      with:
-        path: ${{ runner.tool_cache }}/qemu
-        key: qemu-${{ matrix.target }}-${{ env.QEMU_BUILD_VERSION }}-patchcpuinfo
-      if: matrix.target != '' && matrix.os == 'ubuntu-latest'
     - name: Install cross-compilation tools
       run: |
         set -ex
+
         sudo apt-get update
         sudo apt-get install -y ${{ matrix.gcc_package }} ninja-build
 
         # Configure Cargo for cross compilation and tell it how it can run
         # cross executables
         upcase=$(echo ${{ matrix.target }} | awk '{ print toupper($0) }' | sed 's/-/_/g')
-        echo CARGO_TARGET_${upcase}_RUNNER=${{ runner.tool_cache }}/qemu/bin/${{ matrix.qemu }} >> $GITHUB_ENV
         echo CARGO_TARGET_${upcase}_LINKER=${{ matrix.gcc }} >> $GITHUB_ENV
+      if: matrix.gcc != ''
+
+    - uses: actions/cache@v4
+      with:
+        path: ${{ runner.tool_cache }}/qemu
+        key: qemu-${{ matrix.target }}-${{ env.QEMU_BUILD_VERSION }}-patchcpuinfo
+      if: matrix.qemu != ''
+    - name: Install qemu
+      run: |
+        set -ex
+
+        upcase=$(echo ${{ matrix.target }} | awk '{ print toupper($0) }' | sed 's/-/_/g')
+        echo CARGO_TARGET_${upcase}_RUNNER=${{ runner.tool_cache }}/qemu/bin/${{ matrix.qemu }} >> $GITHUB_ENV
 
         # QEMU emulation is not always the speediest, so total testing time
         # goes down if we build the libs in release mode when running tests.
@@ -643,7 +652,7 @@ jobs:
         ./configure --target-list=${{ matrix.qemu_target }} --prefix=${{ runner.tool_cache}}/qemu --disable-tools --disable-slirp --disable-fdt --disable-capstone --disable-docs
         ninja -C build install
         touch ${{ runner.tool_cache }}/qemu/built
-      if: matrix.gcc != ''
+      if: matrix.qemu != ''
 
     # Record some CPU details; this is helpful information if tests fail due
     # to CPU-specific features.
@@ -817,7 +826,7 @@ jobs:
       if: failure() && github.event_name != 'pull_request'
       env:
         GH_TOKEN: ${{ github.token }}
-  
+
   build-preview1-component-adapter-provider:
     name: Build wasi-preview1-component-adapter-provider
     needs: build-preview1-component-adapter
@@ -830,7 +839,7 @@ jobs:
     - uses: ./.github/actions/build-adapter-provider
       with:
         run-id: ${{ github.run_id }}
-    
+
 
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs
index 24982c644..f96d678b8 100644
--- a/cranelift/codegen/src/ir/immediates.rs
+++ b/cranelift/codegen/src/ir/immediates.rs
@@ -468,65 +468,338 @@ 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);
+// FIXME(rust-lang/rust#83527): Replace with `${ignore()}` once it is stabilised.
+macro_rules! ignore {
+    ($($t:tt)*) => {};
+}
+
+macro_rules! ieee_float {
+    (
+        name = $name:ident,
+        bits = $bits:literal,
+        significand_bits = $significand_bits:literal,
+        bits_ty = $bits_ty:ident,
+        float_ty = $float_ty:ident,
+        $(as_float = $as_float:ident,)?
+        $(rust_type_not_stable = $rust_type_not_stable:ident,)?
+    ) => {
+        /// An IEEE
+        #[doc = concat!("binary", stringify!($bits))]
+        /// immediate floating point value, represented as a
+        #[doc = stringify!($bits_ty)]
+        /// containing the bit pattern.
+        ///
+        /// We specifically avoid using a
+        #[doc = stringify!($float_ty)]
+        /// 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 $name {
+            bits: $bits_ty
+        }
 
-/// An IEEE binary32 immediate floating point value, represented as a u32
-/// containing the bit pattern.
-///
-/// We specifically avoid using a f32 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 Ieee32(u32);
+        impl $name {
+            const BITS: u8 = $bits;
+            const SIGNIFICAND_BITS: u8 = $significand_bits;
+            const EXPONENT_BITS: u8 = Self::BITS - Self::SIGNIFICAND_BITS - 1;
+            const SIGN_MASK: $bits_ty = 1 << (Self::EXPONENT_BITS + Self::SIGNIFICAND_BITS);
+            const SIGNIFICAND_MASK: $bits_ty = $bits_ty::MAX >> (Self::EXPONENT_BITS + 1);
+            const EXPONENT_MASK: $bits_ty = !Self::SIGN_MASK & !Self::SIGNIFICAND_MASK;
+
+            /// Create a new
+            #[doc = concat!("`", stringify!($name), "`")]
+            /// containing the bits of `bits`.
+            pub fn with_bits(bits: $bits_ty) -> Self {
+                Self { bits }
+            }
 
-/// An IEEE binary64 immediate floating point value, represented as a u64
-/// containing the bit pattern.
-///
-/// We specifically avoid using a f64 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 Ieee64(u64);
+            /// Get the bitwise representation.
+            pub fn bits(self) -> $bits_ty {
+                self.bits
+            }
 
-/// 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);
+            $(
+                /// Create a new
+                #[doc = concat!("`", stringify!($name), "`")]
+                /// representing the number `x`.
+                pub fn with_float(x: $float_ty) -> Self {
+                    Self::with_bits(x.to_bits())
+                }
+
+                /// Converts `self` to a Rust
+                #[doc = concat!("`", stringify!($float_ty), "`.")]
+                pub fn $as_float(self) -> $float_ty {
+                    $float_ty::from_bits(self.bits())
+                }
+            )?
+
+            /// Computes the absolute value of `self`.
+            pub fn abs(self) -> Self {
+                Self::with_bits(self.bits() & !Self::SIGN_MASK)
+            }
+
+            /// Returns a number composed of the magnitude of `self` and the sign of `sign`.
+            pub fn copysign(self, sign: Self) -> Self {
+                Self::with_bits((self.bits() & !Self::SIGN_MASK) | (sign.bits() & Self::SIGN_MASK))
+            }
+
+            /// Create an
+            #[doc = concat!("`", stringify!($name), "`")]
+            /// number representing `2.0^n`.
+            pub fn pow2<I: Into<i32>>(n: I) -> Self {
+                let n = n.into();
+                let w = Self::EXPONENT_BITS;
+                let t = Self::SIGNIFICAND_BITS;
+                let bias = (1 << (w - 1)) - 1;
+                let exponent = n + bias;
+                assert!(exponent > 0, "Underflow n={}", n);
+                assert!(exponent < (1 << w) + 1, "Overflow n={}", n);
+                Self::with_bits((exponent as $bits_ty) << t)
+            }
+
+            /// Create an
+            #[doc = concat!("`", stringify!($name), "`")]
+            /// number representing the greatest negative value not convertible from
+            #[doc = concat!("`", stringify!($float_ty), "`")]
+            /// to a signed integer with width n.
+            pub fn fcvt_to_sint_negative_overflow<I: Into<i32>>(n: I) -> Self {
+                let n = n.into();
+                debug_assert!(n < i32::from(Self::BITS));
+                debug_assert!(i32::from(Self::SIGNIFICAND_BITS) + 1 - n < i32::from(Self::BITS));
+                Self::with_bits((1 << (Self::BITS - 1)) | Self::pow2(n - 1).bits() | (1 << (i32::from(Self::SIGNIFICAND_BITS) + 1 - n)))
+            }
+
+            /// Check if the value is a NaN. For
+            #[doc = concat!("`", stringify!($name), "`,")]
+            /// this means checking that all the exponent bits are set and the significand is non-zero.
+            pub fn is_nan(self) -> bool {
+                self.abs().bits() > Self::EXPONENT_MASK
+            }
+
+            /// Returns true if `self` has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
+            pub fn is_negative(self) -> bool {
+                self.bits() & Self::SIGN_MASK == Self::SIGN_MASK
+            }
+
+            /// Returns `true` if `self` is positive or negative zero.
+            pub fn is_zero(self) -> bool {
+                self.abs().bits() == 0
+            }
+
+            /// Returns `None` if `self` is a NaN and `Some(self)` otherwise.
+            pub fn non_nan(self) -> Option<Self> {
+                Some(self).filter(|f| !f.is_nan())
+            }
+
+            $(
+                /// Returns the square root of `self`.
+                pub fn sqrt(self) -> Self {
+                    Self::with_float(self.$as_float().sqrt())
+                }
+
+                /// Returns the smallest integer greater than or equal to `self`.
+                pub fn ceil(self) -> Self {
+                    Self::with_float(self.$as_float().ceil())
+                }
+
+                /// Returns the largest integer less than or equal to `self`.
+                pub fn floor(self) -> Self {
+                    Self::with_float(self.$as_float().floor())
+                }
+
+                /// Returns the integer part of `self`. This means that non-integer numbers are always truncated towards zero.
+                pub fn trunc(self) -> Self {
+                    Self::with_float(self.$as_float().trunc())
+                }
+
+                /// Returns the nearest integer to `self`. Rounds half-way cases to the number
+                /// with an even least significant digit.
+                pub fn round_ties_even(self) -> Self {
+                    Self::with_float(self.$as_float().round_ties_even())
+                }
+            )?
+        }
+
+        impl PartialOrd for $name {
+            fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+                $(self.$as_float().partial_cmp(&rhs.$as_float()))?
+                $(
+                    ignore!($rust_type_not_stable);
+                    // FIXME(#8312): Use builtin Rust comparisons once `f16` and `f128` support is stabalised.
+                    if self.is_nan() || rhs.is_nan() {
+                        // One of the floats is a NaN.
+                        return None;
+                    }
+                    if self.is_zero() || rhs.is_zero() {
+                        // Zeros are always equal regardless of sign.
+                        return Some(Ordering::Equal);
+                    }
+                    let lhs_positive = !self.is_negative();
+                    let rhs_positive = !rhs.is_negative();
+                    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 {
+                        self.bits().partial_cmp(&rhs.bits())
+                    } else {
+                        // Reverse the comparison when both floats are negative.
+                        rhs.bits().partial_cmp(&self.bits())
+                    }
+                )?
+            }
+        }
+
+        impl Display for $name {
+            fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+                format_float(u128::from(self.bits()), Self::EXPONENT_BITS, Self::SIGNIFICAND_BITS, f)
+            }
+        }
+
+        impl FromStr for $name {
+            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::with_bits(b.try_into().unwrap())),
+                    Err(s) => Err(s),
+                }
+            }
+        }
+
+        impl IntoBytes for $name {
+            fn into_bytes(self) -> Vec<u8> {
+                self.bits().to_le_bytes().to_vec()
+            }
+        }
+
+        impl Neg for $name {
+            type Output = Self;
+
+            fn neg(self) -> Self {
+                Self::with_bits(self.bits() ^ Self::SIGN_MASK)
+            }
+        }
+
+
+
+        $(
+            impl From<$float_ty> for $name {
+                fn from(x: $float_ty) -> Self {
+                    Self::with_float(x)
+                }
+            }
+
+            impl Add for $name {
+                type Output = Self;
+
+                fn add(self, rhs: Self) -> Self {
+                    Self::with_float(self.$as_float() + rhs.$as_float())
+                }
+            }
+
+            impl Sub for $name {
+                type Output = Self;
+
+                fn sub(self, rhs: Self) -> Self {
+                    Self::with_float(self.$as_float() - rhs.$as_float())
+                }
+            }
+
+            impl Mul for $name {
+                type Output = Self;
+
+                fn mul(self, rhs: Self) -> Self {
+                    Self::with_float(self.$as_float() * rhs.$as_float())
+                }
+            }
+
+            impl Div for $name {
+                type Output = Self;
+
+                fn div(self, rhs: Self) -> Self::Output {
+                    Self::with_float(self.$as_float() / rhs.$as_float())
+                }
+            }
+        )?
+
+        impl BitAnd for $name {
+            type Output = Self;
+
+            fn bitand(self, rhs: Self) -> Self {
+                Self::with_bits(self.bits() & rhs.bits())
+            }
+        }
+
+        impl BitOr for $name {
+            type Output = Self;
+
+            fn bitor(self, rhs: Self) -> Self {
+                Self::with_bits(self.bits() | rhs.bits())
+            }
+        }
+
+        impl BitXor for $name {
+            type Output = Self;
+
+            fn bitxor(self, rhs: Self) -> Self {
+                Self::with_bits(self.bits() ^ rhs.bits())
+            }
+        }
+
+        impl Not for $name {
+            type Output = Self;
+
+            fn not(self) -> Self {
+                Self::with_bits(!self.bits())
+            }
+        }
+    };
+}
+
+ieee_float! {
+    name = Ieee16,
+    bits = 16,
+    significand_bits = 10,
+    bits_ty = u16,
+    float_ty = f16,
+    rust_type_not_stable = rust_type_not_stable,
+}
+
+ieee_float! {
+    name = Ieee32,
+    bits = 32,
+    significand_bits = 23,
+    bits_ty = u32,
+    float_ty = f32,
+    as_float = as_f32,
+}
+
+ieee_float! {
+    name = Ieee64,
+    bits = 64,
+    significand_bits = 52,
+    bits_ty = u64,
+    float_ty = f64,
+    as_float = as_f64,
+}
+
+ieee_float! {
+    name = Ieee128,
+    bits = 128,
+    significand_bits = 112,
+    bits_ty = u128,
+    float_ty = f128,
+    rust_type_not_stable = rust_type_not_stable,
+}
 
 /// 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
@@ -765,651 +1038,10 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result<u128, &'static str> {
     }
 }
 
-impl Ieee16 {
-    const SIGNIFICAND_BITS: u8 = 10;
-    const EXPONENT_BITS: u8 = 5;
-    const SIGN_MASK: u16 = 1 << (Self::EXPONENT_BITS + Self::SIGNIFICAND_BITS);
-    const SIGNIFICAND_MASK: u16 = u16::MAX >> (Self::EXPONENT_BITS + 1);
-    const EXPONENT_MASK: u16 = !Self::SIGN_MASK & !Self::SIGNIFICAND_MASK;
-
-    /// 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
-    }
-
-    /// Computes the absolute value of self.
-    pub fn abs(self) -> Self {
-        Self::with_bits(self.bits() & !Self::SIGN_MASK)
-    }
-
-    /// Returns a number composed of the magnitude of self and the sign of sign.
-    pub fn copysign(self, sign: Self) -> Self {
-        Self::with_bits((self.bits() & !Self::SIGN_MASK) | (sign.bits() & Self::SIGN_MASK))
-    }
-
-    /// 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 lhs_abs = self.bits() & !Self::SIGN_MASK;
-        let rhs_abs = rhs.bits() & !Self::SIGN_MASK;
-        if (lhs_abs & Self::EXPONENT_MASK == Self::EXPONENT_MASK
-            && lhs_abs & Self::SIGNIFICAND_MASK != 0)
-            && (rhs_abs & Self::EXPONENT_MASK == Self::EXPONENT_MASK
-                && rhs_abs & Self::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() & Self::SIGN_MASK == 0;
-        let rhs_positive = rhs.bits() & Self::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 IntoBytes for Ieee16 {
-    fn into_bytes(self) -> Vec<u8> {
-        self.bits().to_le_bytes().to_vec()
-    }
-}
-
-impl Neg for Ieee16 {
-    type Output = Self;
-
-    fn neg(self) -> Self {
-        Self::with_bits(self.bits() ^ Self::SIGN_MASK)
-    }
-}
-
-impl Ieee32 {
-    /// Create a new `Ieee32` containing the bits of `x`.
-    pub fn with_bits(x: u32) -> Self {
-        Self(x)
-    }
-
-    /// Create an `Ieee32` number representing `2.0^n`.
-    pub fn pow2<I: Into<i32>>(n: I) -> Self {
-        let n = n.into();
-        let w = 8;
-        let t = 23;
-        let bias = (1 << (w - 1)) - 1;
-        let exponent = (n + bias) as u32;
-        assert!(exponent > 0, "Underflow n={}", n);
-        assert!(exponent < (1 << w) + 1, "Overflow n={}", n);
-        Self(exponent << t)
-    }
-
-    /// Create an `Ieee32` number representing the greatest negative value
-    /// not convertible from f32 to a signed integer with width n.
-    pub fn fcvt_to_sint_negative_overflow<I: Into<i32>>(n: I) -> Self {
-        let n = n.into();
-        debug_assert!(n < 32);
-        debug_assert!(23 + 1 - n < 32);
-        Self::with_bits((1u32 << (32 - 1)) | Self::pow2(n - 1).0 | (1u32 << (23 + 1 - n)))
-    }
-
-    /// Return self negated.
-    pub fn neg(self) -> Self {
-        Self(self.0 ^ (1 << 31))
-    }
-
-    /// Create a new `Ieee32` representing the number `x`.
-    pub fn with_float(x: f32) -> Self {
-        Self(x.to_bits())
-    }
-
-    /// Get the bitwise representation.
-    pub fn bits(self) -> u32 {
-        self.0
-    }
-
-    /// Check if the value is a NaN.
-    pub fn is_nan(&self) -> bool {
-        self.as_f32().is_nan()
-    }
-
-    /// Returns `None` if `self` is a NaN and `Some(self)` otherwise.
-    pub fn non_nan(self) -> Option<Self> {
-        Some(self).filter(|f| !f.is_nan())
-    }
-
-    /// Converts Self to a rust f32
-    pub fn as_f32(self) -> f32 {
-        f32::from_bits(self.0)
-    }
-
-    /// Returns the square root of self.
-    pub fn sqrt(self) -> Self {
-        Self::with_float(self.as_f32().sqrt())
-    }
-
-    /// Computes the absolute value of self.
-    pub fn abs(self) -> Self {
-        Self(self.0 & !(1u32 << 31))
-    }
-
-    /// Returns a number composed of the magnitude of self and the sign of sign.
-    pub fn copysign(self, sign: Self) -> Self {
-        if self.is_negative() == sign.is_negative() {
-            self
-        } else {
-            self.neg()
-        }
-    }
-
-    /// Returns true if self has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
-    pub fn is_negative(&self) -> bool {
-        self.0 & (1 << 31) != 0
-    }
-
-    /// Returns true if self is positive or negative zero
-    pub fn is_zero(&self) -> bool {
-        self.as_f32() == 0.0
-    }
-
-    /// Returns the smallest integer greater than or equal to `self`.
-    pub fn ceil(self) -> Self {
-        Self::with_float(self.as_f32().ceil())
-    }
-
-    /// Returns the largest integer less than or equal to `self`.
-    pub fn floor(self) -> Self {
-        Self::with_float(self.as_f32().floor())
-    }
-
-    /// Returns the integer part of `self`. This means that non-integer numbers are always truncated towards zero.
-    pub fn trunc(self) -> Self {
-        Self::with_float(self.as_f32().trunc())
-    }
-
-    /// Returns the nearest integer to `self`. Rounds half-way cases to the number
-    /// with an even least significant digit.
-    pub fn round_ties_even(self) -> Self {
-        Self::with_float(self.as_f32().round_ties_even())
-    }
-}
-
-impl PartialOrd for Ieee32 {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.as_f32().partial_cmp(&other.as_f32())
-    }
-}
-
-impl Display for Ieee32 {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        let bits: u32 = self.0;
-        format_float(u128::from(bits), 8, 23, f)
-    }
-}
-
-impl FromStr for Ieee32 {
-    type Err = &'static str;
-
-    fn from_str(s: &str) -> Result<Self, &'static str> {
-        match parse_float(s, 8, 23) {
-            Ok(b) => Ok(Self(b as u32)),
-            Err(s) => Err(s),
-        }
-    }
-}
-
-impl From<f32> for Ieee32 {
-    fn from(x: f32) -> Self {
-        Self::with_float(x)
-    }
-}
-
-impl IntoBytes for Ieee32 {
-    fn into_bytes(self) -> Vec<u8> {
-        self.0.to_le_bytes().to_vec()
-    }
-}
-
-impl Neg for Ieee32 {
-    type Output = Ieee32;
-
-    fn neg(self) -> Self::Output {
-        Self(self.0 ^ (1 << 31))
-    }
-}
-
-impl Add for Ieee32 {
-    type Output = Ieee32;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f32() + rhs.as_f32())
-    }
-}
-
-impl Sub for Ieee32 {
-    type Output = Ieee32;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f32() - rhs.as_f32())
-    }
-}
-
-impl Mul for Ieee32 {
-    type Output = Ieee32;
-
-    fn mul(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f32() * rhs.as_f32())
-    }
-}
-
-impl Div for Ieee32 {
-    type Output = Ieee32;
-
-    fn div(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f32() / rhs.as_f32())
-    }
-}
-
-impl BitAnd for Ieee32 {
-    type Output = Ieee32;
-
-    fn bitand(self, rhs: Self) -> Self::Output {
-        Self::with_bits(self.bits() & rhs.bits())
-    }
-}
-
-impl BitOr for Ieee32 {
-    type Output = Ieee32;
-
-    fn bitor(self, rhs: Self) -> Self::Output {
-        Self::with_bits(self.bits() | rhs.bits())
-    }
-}
-
-impl BitXor for Ieee32 {
-    type Output = Ieee32;
-
-    fn bitxor(self, rhs: Self) -> Self::Output {
-        Self::with_bits(self.bits() ^ rhs.bits())
-    }
-}
-
-impl Not for Ieee32 {
-    type Output = Ieee32;
-
-    fn not(self) -> Self::Output {
-        Self::with_bits(!self.bits())
-    }
-}
-
-impl Ieee64 {
-    /// Create a new `Ieee64` containing the bits of `x`.
-    pub fn with_bits(x: u64) -> Self {
-        Self(x)
-    }
-
-    /// Create an `Ieee64` number representing `2.0^n`.
-    pub fn pow2<I: Into<i64>>(n: I) -> Self {
-        let n = n.into();
-        let w = 11;
-        let t = 52;
-        let bias = (1 << (w - 1)) - 1;
-        let exponent = (n + bias) as u64;
-        assert!(exponent > 0, "Underflow n={}", n);
-        assert!(exponent < (1 << w) + 1, "Overflow n={}", n);
-        Self(exponent << t)
-    }
-
-    /// Create an `Ieee64` number representing the greatest negative value
-    /// not convertible from f64 to a signed integer with width n.
-    pub fn fcvt_to_sint_negative_overflow<I: Into<i64>>(n: I) -> Self {
-        let n = n.into();
-        debug_assert!(n < 64);
-        debug_assert!(52 + 1 - n < 64);
-        Self::with_bits((1u64 << (64 - 1)) | Self::pow2(n - 1).0 | (1u64 << (52 + 1 - n)))
-    }
-
-    /// Return self negated.
-    pub fn neg(self) -> Self {
-        Self(self.0 ^ (1 << 63))
-    }
-
-    /// Create a new `Ieee64` representing the number `x`.
-    pub fn with_float(x: f64) -> Self {
-        Self(x.to_bits())
-    }
-
-    /// Get the bitwise representation.
-    pub fn bits(self) -> u64 {
-        self.0
-    }
-
-    /// Check if the value is a NaN. For [Ieee64], this means checking that the 11 exponent bits are
-    /// all set.
-    pub fn is_nan(&self) -> bool {
-        self.as_f64().is_nan()
-    }
-
-    /// Returns `None` if `self` is a NaN and `Some(self)` otherwise.
-    pub fn non_nan(self) -> Option<Self> {
-        Some(self).filter(|f| !f.is_nan())
-    }
-
-    /// Converts Self to a rust f64
-    pub fn as_f64(self) -> f64 {
-        f64::from_bits(self.0)
-    }
-
-    /// Returns the square root of self.
-    pub fn sqrt(self) -> Self {
-        Self::with_float(self.as_f64().sqrt())
-    }
-
-    /// Computes the absolute value of self.
-    pub fn abs(self) -> Self {
-        Self(self.0 & !(1u64 << 63))
-    }
-
-    /// Returns a number composed of the magnitude of self and the sign of sign.
-    pub fn copysign(self, sign: Self) -> Self {
-        if self.is_negative() == sign.is_negative() {
-            self
-        } else {
-            self.neg()
-        }
-    }
-
-    /// Returns true if self has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
-    pub fn is_negative(&self) -> bool {
-        self.0 & (1 << 63) != 0
-    }
-
-    /// Returns true if self is positive or negative zero
-    pub fn is_zero(&self) -> bool {
-        self.as_f64() == 0.0
-    }
-
-    /// Returns the smallest integer greater than or equal to `self`.
-    pub fn ceil(self) -> Self {
-        Self::with_float(self.as_f64().ceil())
-    }
-
-    /// Returns the largest integer less than or equal to `self`.
-    pub fn floor(self) -> Self {
-        Self::with_float(self.as_f64().floor())
-    }
-
-    /// Returns the integer part of `self`. This means that non-integer numbers are always truncated towards zero.
-    pub fn trunc(self) -> Self {
-        Self::with_float(self.as_f64().trunc())
-    }
-
-    /// Returns the nearest integer to `self`. Rounds half-way cases to the number
-    /// with an even least significant digit.
-    pub fn round_ties_even(self) -> Self {
-        Self::with_float(self.as_f64().round_ties_even())
-    }
-}
-
-impl PartialOrd for Ieee64 {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.as_f64().partial_cmp(&other.as_f64())
-    }
-}
-
-impl Display for Ieee64 {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        let bits: u64 = self.0;
-        format_float(u128::from(bits), 11, 52, f)
-    }
-}
-
-impl FromStr for Ieee64 {
-    type Err = &'static str;
-
-    fn from_str(s: &str) -> Result<Self, &'static str> {
-        match parse_float(s, 11, 52) {
-            Ok(b) => Ok(Self(b as u64)),
-            Err(s) => Err(s),
-        }
-    }
-}
-
-impl From<f64> for Ieee64 {
-    fn from(x: f64) -> Self {
-        Self::with_float(x)
-    }
-}
-
-impl From<u64> for Ieee64 {
-    fn from(x: u64) -> Self {
-        Self::with_float(f64::from_bits(x))
-    }
-}
-
-impl IntoBytes for Ieee64 {
-    fn into_bytes(self) -> Vec<u8> {
-        self.0.to_le_bytes().to_vec()
-    }
-}
-
-impl Neg for Ieee64 {
-    type Output = Ieee64;
-
-    fn neg(self) -> Self::Output {
-        Self(self.0 ^ (1 << 63))
-    }
-}
-
-impl Add for Ieee64 {
-    type Output = Ieee64;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f64() + rhs.as_f64())
-    }
-}
-
-impl Sub for Ieee64 {
-    type Output = Ieee64;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f64() - rhs.as_f64())
-    }
-}
-
-impl Mul for Ieee64 {
-    type Output = Ieee64;
-
-    fn mul(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f64() * rhs.as_f64())
-    }
-}
-
-impl Div for Ieee64 {
-    type Output = Ieee64;
-
-    fn div(self, rhs: Self) -> Self::Output {
-        Self::with_float(self.as_f64() / rhs.as_f64())
-    }
-}
-
-impl BitAnd for Ieee64 {
-    type Output = Ieee64;
-
-    fn bitand(self, rhs: Self) -> Self::Output {
-        Self::with_bits(self.bits() & rhs.bits())
-    }
-}
-
-impl BitOr for Ieee64 {
-    type Output = Ieee64;
-
-    fn bitor(self, rhs: Self) -> Self::Output {
-        Self::with_bits(self.bits() | rhs.bits())
-    }
-}
-
-impl BitXor for Ieee64 {
-    type Output = Ieee64;
-
-    fn bitxor(self, rhs: Self) -> Self::Output {
-        Self::with_bits(self.bits() ^ rhs.bits())
-    }
-}
-
-impl Not for Ieee64 {
-    type Output = Ieee64;
-
-    fn not(self) -> Self::Output {
-        Self::with_bits(!self.bits())
-    }
-}
-
-impl Ieee128 {
-    const SIGNIFICAND_BITS: u8 = 112;
-    const EXPONENT_BITS: u8 = 15;
-    const SIGN_MASK: u128 = 1 << (Self::EXPONENT_BITS + Self::SIGNIFICAND_BITS);
-    const SIGNIFICAND_MASK: u128 = u128::MAX >> (Self::EXPONENT_BITS + 1);
-    const EXPONENT_MASK: u128 = !Self::SIGN_MASK & !Self::SIGNIFICAND_MASK;
-
-    /// 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
-    }
-
-    /// Computes the absolute value of self.
-    pub fn abs(self) -> Self {
-        Self::with_bits(self.bits() & !Self::SIGN_MASK)
-    }
-
-    /// Returns a number composed of the magnitude of self and the sign of sign.
-    pub fn copysign(self, sign: Self) -> Self {
-        Self::with_bits((self.bits() & !Self::SIGN_MASK) | (sign.bits() & Self::SIGN_MASK))
-    }
-
-    /// 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 lhs_abs = self.bits() & !Self::SIGN_MASK;
-        let rhs_abs = rhs.bits() & !Self::SIGN_MASK;
-        if (lhs_abs & Self::EXPONENT_MASK == Self::EXPONENT_MASK
-            && lhs_abs & Self::SIGNIFICAND_MASK != 0)
-            && (rhs_abs & Self::EXPONENT_MASK == Self::EXPONENT_MASK
-                && rhs_abs & Self::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() & Self::SIGN_MASK == 0;
-        let rhs_positive = rhs.bits() & Self::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),
-        }
-    }
-}
-
-impl IntoBytes for Ieee128 {
-    fn into_bytes(self) -> Vec<u8> {
-        self.bits().to_le_bytes().to_vec()
-    }
-}
-
-impl Neg for Ieee128 {
-    type Output = Self;
-
-    fn neg(self) -> Self {
-        Self::with_bits(self.bits() ^ Self::SIGN_MASK)
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
     use alloc::string::ToString;
-    use core::mem;
     use core::{f32, f64};
 
     #[test]
@@ -1625,11 +1257,11 @@ mod tests {
             "-NaN"
         );
         // Construct some qNaNs with payloads.
-        assert_eq!(Ieee16(0x7e01).to_string(), "+NaN:0x1");
-        assert_eq!(Ieee16(0x7f01).to_string(), "+NaN:0x101");
+        assert_eq!(Ieee16::with_bits(0x7e01).to_string(), "+NaN:0x1");
+        assert_eq!(Ieee16::with_bits(0x7f01).to_string(), "+NaN:0x101");
         // Signaling NaNs.
-        assert_eq!(Ieee16(0x7c01).to_string(), "+sNaN:0x1");
-        assert_eq!(Ieee16(0x7d01).to_string(), "+sNaN:0x101");
+        assert_eq!(Ieee16::with_bits(0x7c01).to_string(), "+sNaN:0x1");
+        assert_eq!(Ieee16::with_bits(0x7d01).to_string(), "+sNaN:0x101");
     }
 
     #[test]
@@ -1703,6 +1335,32 @@ mod tests {
         parse_err::<Ieee16>("sNaN:0x301", "Invalid sNaN payload");
     }
 
+    #[test]
+    fn pow2_ieee16() {
+        assert_eq!(Ieee16::pow2(0).to_string(), "0x1.000p0");
+        assert_eq!(Ieee16::pow2(1).to_string(), "0x1.000p1");
+        assert_eq!(Ieee16::pow2(-1).to_string(), "0x1.000p-1");
+        assert_eq!(Ieee16::pow2(15).to_string(), "0x1.000p15");
+        assert_eq!(Ieee16::pow2(-14).to_string(), "0x1.000p-14");
+
+        assert_eq!((-Ieee16::pow2(1)).to_string(), "-0x1.000p1");
+    }
+
+    #[test]
+    fn fcvt_to_sint_negative_overflow_ieee16() {
+        // FIXME(#8312): Replace with commented out version once Rust f16 support is stabilised.
+        // let n = 8;
+        // assert_eq!(
+        //     -((1u16 << (n - 1)) as f16) - 1.0,
+        //     Ieee16::fcvt_to_sint_negative_overflow(n).as_f16()
+        // );
+        let n = 8;
+        assert_eq!(
+            "-0x1.020p7",
+            Ieee16::fcvt_to_sint_negative_overflow(n).to_string()
+        );
+    }
+
     #[test]
     fn format_ieee32() {
         assert_eq!(Ieee32::with_float(0.0).to_string(), "0.0");
@@ -1735,11 +1393,11 @@ mod tests {
         assert_eq!(Ieee32::with_float(f32::NAN).to_string(), "+NaN");
         assert_eq!(Ieee32::with_float(-f32::NAN).to_string(), "-NaN");
         // Construct some qNaNs with payloads.
-        assert_eq!(Ieee32(0x7fc00001).to_string(), "+NaN:0x1");
-        assert_eq!(Ieee32(0x7ff00001).to_string(), "+NaN:0x300001");
+        assert_eq!(Ieee32::with_bits(0x7fc00001).to_string(), "+NaN:0x1");
+        assert_eq!(Ieee32::with_bits(0x7ff00001).to_string(), "+NaN:0x300001");
         // Signaling NaNs.
-        assert_eq!(Ieee32(0x7f800001).to_string(), "+sNaN:0x1");
-        assert_eq!(Ieee32(0x7fa00001).to_string(), "+sNaN:0x200001");
+        assert_eq!(Ieee32::with_bits(0x7f800001).to_string(), "+sNaN:0x1");
+        assert_eq!(Ieee32::with_bits(0x7fa00001).to_string(), "+sNaN:0x200001");
     }
 
     #[test]
@@ -1821,15 +1479,17 @@ mod tests {
         assert_eq!(Ieee32::pow2(127).to_string(), "0x1.000000p127");
         assert_eq!(Ieee32::pow2(-126).to_string(), "0x1.000000p-126");
 
-        assert_eq!(Ieee32::pow2(1).neg().to_string(), "-0x1.000000p1");
+        assert_eq!((-Ieee32::pow2(1)).to_string(), "-0x1.000000p1");
     }
 
     #[test]
     fn fcvt_to_sint_negative_overflow_ieee32() {
-        for n in &[8, 16] {
-            assert_eq!(-((1u32 << (n - 1)) as f32) - 1.0, unsafe {
-                mem::transmute(Ieee32::fcvt_to_sint_negative_overflow(*n))
-            });
+        for n in [8, 16] {
+            assert_eq!(
+                -((1u32 << (n - 1)) as f32) - 1.0,
+                Ieee32::fcvt_to_sint_negative_overflow(n).as_f32(),
+                "n = {n}"
+            );
         }
     }
 
@@ -1871,15 +1531,21 @@ mod tests {
         assert_eq!(Ieee64::with_float(f64::NAN).to_string(), "+NaN");
         assert_eq!(Ieee64::with_float(-f64::NAN).to_string(), "-NaN");
         // Construct some qNaNs with payloads.
-        assert_eq!(Ieee64(0x7ff8000000000001).to_string(), "+NaN:0x1");
         assert_eq!(
-            Ieee64(0x7ffc000000000001).to_string(),
+            Ieee64::with_bits(0x7ff8000000000001).to_string(),
+            "+NaN:0x1"
+        );
+        assert_eq!(
+            Ieee64::with_bits(0x7ffc000000000001).to_string(),
             "+NaN:0x4000000000001"
         );
         // Signaling NaNs.
-        assert_eq!(Ieee64(0x7ff0000000000001).to_string(), "+sNaN:0x1");
         assert_eq!(
-            Ieee64(0x7ff4000000000001).to_string(),
+            Ieee64::with_bits(0x7ff0000000000001).to_string(),
+            "+sNaN:0x1"
+        );
+        assert_eq!(
+            Ieee64::with_bits(0x7ff4000000000001).to_string(),
             "+sNaN:0x4000000000001"
         );
     }
@@ -1959,15 +1625,17 @@ mod tests {
         assert_eq!(Ieee64::pow2(1023).to_string(), "0x1.0000000000000p1023");
         assert_eq!(Ieee64::pow2(-1022).to_string(), "0x1.0000000000000p-1022");
 
-        assert_eq!(Ieee64::pow2(1).neg().to_string(), "-0x1.0000000000000p1");
+        assert_eq!((-Ieee64::pow2(1)).to_string(), "-0x1.0000000000000p1");
     }
 
     #[test]
     fn fcvt_to_sint_negative_overflow_ieee64() {
-        for n in &[8, 16, 32] {
-            assert_eq!(-((1u64 << (n - 1)) as f64) - 1.0, unsafe {
-                mem::transmute(Ieee64::fcvt_to_sint_negative_overflow(*n))
-            });
+        for n in [8, 16, 32] {
+            assert_eq!(
+                -((1u64 << (n - 1)) as f64) - 1.0,
+                Ieee64::fcvt_to_sint_negative_overflow(n).as_f64(),
+                "n = {n}"
+            );
         }
     }
 
@@ -2037,20 +1705,20 @@ mod tests {
         );
         // Construct some qNaNs with payloads.
         assert_eq!(
-            Ieee128(0x7fff8000000000000000000000000001).to_string(),
+            Ieee128::with_bits(0x7fff8000000000000000000000000001).to_string(),
             "+NaN:0x1"
         );
         assert_eq!(
-            Ieee128(0x7fffc000000000000000000000000001).to_string(),
+            Ieee128::with_bits(0x7fffc000000000000000000000000001).to_string(),
             "+NaN:0x4000000000000000000000000001"
         );
         // Signaling NaNs.
         assert_eq!(
-            Ieee128(0x7fff0000000000000000000000000001).to_string(),
+            Ieee128::with_bits(0x7fff0000000000000000000000000001).to_string(),
             "+sNaN:0x1"
         );
         assert_eq!(
-            Ieee128(0x7fff4000000000000000000000000001).to_string(),
+            Ieee128::with_bits(0x7fff4000000000000000000000000001).to_string(),
             "+sNaN:0x4000000000000000000000000001"
         );
     }
@@ -2154,4 +1822,57 @@ mod tests {
             "Invalid sNaN payload",
         );
     }
+
+    #[test]
+    fn pow2_ieee128() {
+        assert_eq!(
+            Ieee128::pow2(0).to_string(),
+            "0x1.0000000000000000000000000000p0"
+        );
+        assert_eq!(
+            Ieee128::pow2(1).to_string(),
+            "0x1.0000000000000000000000000000p1"
+        );
+        assert_eq!(
+            Ieee128::pow2(-1).to_string(),
+            "0x1.0000000000000000000000000000p-1"
+        );
+        assert_eq!(
+            Ieee128::pow2(16383).to_string(),
+            "0x1.0000000000000000000000000000p16383"
+        );
+        assert_eq!(
+            Ieee128::pow2(-16382).to_string(),
+            "0x1.0000000000000000000000000000p-16382"
+        );
+
+        assert_eq!(
+            (-Ieee128::pow2(1)).to_string(),
+            "-0x1.0000000000000000000000000000p1"
+        );
+    }
+
+    #[test]
+    fn fcvt_to_sint_negative_overflow_ieee128() {
+        // FIXME(#8312): Replace with commented out version once Rust f128 support is stabilised.
+        // for n in [8, 16, 32, 64] {
+        //     assert_eq!(
+        //         -((1u128 << (n - 1)) as f128) - 1.0,
+        //         Ieee128::fcvt_to_sint_negative_overflow(n).as_f128(),
+        //         "n = {n}"
+        //     );
+        // }
+        for (n, expected) in [
+            (8, "-0x1.0200000000000000000000000000p7"),
+            (16, "-0x1.0002000000000000000000000000p15"),
+            (32, "-0x1.0000000200000000000000000000p31"),
+            (64, "-0x1.0000000000000002000000000000p63"),
+        ] {
+            assert_eq!(
+                expected,
+                Ieee128::fcvt_to_sint_negative_overflow(n).to_string(),
+                "n = {n}"
+            );
+        }
+    }
 }
diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs
index d46e18a40..45ae37c96 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit.rs
@@ -3577,7 +3577,7 @@ pub(crate) fn emit(
                 let output_bits = dst_size.to_bits();
                 match *src_size {
                     OperandSize::Size32 => {
-                        let cst = Ieee32::pow2(output_bits - 1).neg().bits();
+                        let cst = (-Ieee32::pow2(output_bits - 1)).bits();
                         let inst = Inst::imm(OperandSize::Size32, cst as u64, tmp_gpr);
                         inst.emit(sink, info, state);
                     }
@@ -3588,7 +3588,7 @@ pub(crate) fn emit(
                             no_overflow_cc = CC::NBE; // >
                             Ieee64::fcvt_to_sint_negative_overflow(output_bits)
                         } else {
-                            Ieee64::pow2(output_bits - 1).neg()
+                            -Ieee64::pow2(output_bits - 1)
                         };
                         let inst = Inst::imm(OperandSize::Size64, cst.bits(), tmp_gpr);
                         inst.emit(sink, info, state);
diff --git a/cranelift/codegen/src/isle_prelude.rs b/cranelift/codegen/src/isle_prelude.rs
index 530cfb139..ce6879cb7 100644
--- a/cranelift/codegen/src/isle_prelude.rs
+++ b/cranelift/codegen/src/isle_prelude.rs
@@ -1007,7 +1007,7 @@ macro_rules! isle_common_prelude_methods {
         }
 
         fn f32_neg(&mut self, n: Ieee32) -> Ieee32 {
-            n.neg()
+            -n
         }
 
         fn f32_abs(&mut self, n: Ieee32) -> Ieee32 {
@@ -1075,7 +1075,7 @@ macro_rules! isle_common_prelude_methods {
         }
 
         fn f64_neg(&mut self, n: Ieee64) -> Ieee64 {
-            n.neg()
+            -n
         }
 
         fn f64_abs(&mut self, n: Ieee64) -> Ieee64 {
diff --git a/cranelift/codegen/src/verifier/mod.rs b/cranelift/codegen/src/verifier/mod.rs
index 457c9e3a9..f750280f6 100644
--- a/cranelift/codegen/src/verifier/mod.rs
+++ b/cranelift/codegen/src/verifier/mod.rs
@@ -1929,7 +1929,7 @@ mod tests {
         // Build instruction "f64const 0.0" (missing one required result)
         let inst = func.dfg.make_inst(InstructionData::UnaryIeee64 {
             opcode: Opcode::F64const,
-            imm: 0.into(),
+            imm: 0.0.into(),
         });
         func.layout.append_inst(inst, block0);
 
diff --git a/cranelift/interpreter/src/value.rs b/cranelift/interpreter/src/value.rs
index d72ee5714..737e6a008 100644
--- a/cranelift/interpreter/src/value.rs
+++ b/cranelift/interpreter/src/value.rs
@@ -4,6 +4,7 @@
 #![allow(trivial_numeric_casts)]
 
 use core::fmt::{self, Display, Formatter};
+use core::ops::Neg;
 use cranelift_codegen::data_value::{DataValue, DataValueCastFailure};
 use cranelift_codegen::ir::immediates::{Ieee128, Ieee16, Ieee32, Ieee64};
 use cranelift_codegen::ir::{types, Type};
diff --git a/pulley/src/decode.rs b/pulley/src/decode.rs
index 9d609ca44..dcc219836 100644
--- a/pulley/src/decode.rs
+++ b/pulley/src/decode.rs
@@ -402,7 +402,7 @@ impl Decoder {
         V: OpVisitor<BytecodeStream = SafeBytecodeStream<'a>> + ExtendedOpVisitor,
     {
         let mut decoder = Decoder::new();
-        let mut results = vec![];
+        let mut results = Vec::new();
 
         while !visitor.bytecode().as_slice().is_empty() {
             results.push(decoder.decode_one(visitor)?);


Test patch:
diff --git a/ci/build-test-matrix.js b/ci/build-test-matrix.js
index 18692260e..f1236ed9f 100644
--- a/ci/build-test-matrix.js
+++ b/ci/build-test-matrix.js
@@ -121,7 +121,27 @@ const FULL_MATRIX = [
     "name": "Test Linux riscv64",
     "filter": "linux-riscv64",
     "isa": "riscv64",
-  }
+  },
+  {
+    "name": "Test Pulley on i686-unknown-linux-gnu",
+    "32-bit": true,
+    "os": "ubuntu-latest",
+    "target": "i686-unknown-linux-gnu",
+    "gcc_package": "gcc-i686-linux-gnu",
+    "gcc": "i686-linux-gnu-gcc",
+    "filter": "pulley",
+  },
+  {
+    "name": "Test Pulley on armv7-unknown-linux-gnueabihf",
+    "32-bit": true,
+    "os": "ubuntu-latest",
+    "target": "armv7-unknown-linux-gnueabihf",
+    "gcc_package": "gcc-arm-linux-gnueabihf",
+    "gcc": "arm-linux-gnueabihf-gcc",
+    "qemu": "qemu-arm -L /usr/arm-linux-gnueabihf -E LD_LIBRARY_PATH=/usr/arm-linux-gnueabihf/lib",
+    "qemu_target": "arm-linux-user",
+    "filter": "pulley",
+  },
 ];
 
 /// Get the workspace's full list of member crates.
@@ -198,6 +218,20 @@ async function shard(configs) {
   // created above.
   const sharded = [];
   for (const config of configs) {
+    // Special case 32-bit configs. Only Pulley runs on 32-bit targets.
+    if (config["32-bit"] === true) {
+      sharded.push(Object.assign(
+        {},
+        config,
+        {
+          bucket: members
+            .map(c => c.indexOf("pulley") !== -1 ? `--package ${c}` : `--exclude ${c}`)
+            .join(" "),
+        }
+      ));
+      continue;
+    }
+
     for (const bucket of buckets) {
       sharded.push(Object.assign(
         {},
@@ -255,6 +289,11 @@ async function main() {
       return config.isa !== undefined;
     }
 
+    // If any Pulley source file was modified, then run Pulley-specific configs.
+    if (names.includes("pulley") && config.filter == "pulley") {
+      return true;
+    }
+
     // If the commit explicitly asks for this test config, then include it.
     if (config.filter && commits.includes(`prtest:${config.filter}`)) {
       return true;