lexical_parse_float/fpu.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
//! Platform-specific, assembly instructions to avoid
//! intermediate rounding on architectures with FPUs.
//!
//! This is adapted from the implementation in the Rust core library,
//! the original implementation can be [here](https://github.com/rust-lang/rust/blob/master/library/core/src/num/dec2flt/fpu.rs).
//!
//! It is therefore also subject to a Apache2.0/MIT license.
#![doc(hidden)]
pub use fpu_precision::set_precision;
// On x86, the x87 FPU is used for float operations if the SSE/SSE2 extensions
// are not available. The x87 FPU operates with 80 bits of precision by default,
// which means that operations will round to 80 bits causing double rounding to
// happen when values are eventually represented as 32/64 bit float values. To
// overcome this, the FPU control word can be set so that the computations are
// performed in the desired precision.
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
mod fpu_precision {
// We only support the latest nightly, which is 1.59+.
// The `asm!` macro was stabilized in 1.59.0.
use core::arch::asm;
use core::mem::size_of;
/// A structure used to preserve the original value of the FPU control word,
/// so that it can be restored when the structure is dropped.
///
/// The x87 FPU is a 16-bits register whose fields are as follows:
///
/// | 12-15 | 10-11 | 8-9 | 6-7 | 5 | 4 | 3 | 2 | 1 | 0 |
/// |------:|------:|----:|----:|---:|---:|---:|---:|---:|---:|
/// | | RC | PC | | PM | UM | OM | ZM | DM | IM |
///
/// The documentation for all of the fields is available in the IA-32
/// Architectures Software Developer's Manual (Volume 1).
///
/// The only field which is relevant for the following code is PC, Precision
/// Control. This field determines the precision of the operations
/// performed by the FPU. It can be set to:
/// - 0b00, single precision i.e., 32-bits
/// - 0b10, double precision i.e., 64-bits
/// - 0b11, double extended precision i.e., 80-bits (default state)
/// The 0b01 value is reserved and should not be used.
pub struct FPUControlWord(u16);
fn set_cw(cw: u16) {
// SAFETY: the `fldcw` instruction has been audited to be able to work correctly
// with any `u16`
unsafe {
asm!(
"fldcw word ptr [{}]",
in(reg) &cw,
options(nostack),
)
}
}
/// Sets the precision field of the FPU to `T` and returns a
/// `FPUControlWord`.
pub fn set_precision<T>() -> FPUControlWord {
let mut cw = 0_u16;
// Compute the value for the Precision Control field that is appropriate for
// `T`.
let cw_precision = match size_of::<T>() {
4 => 0x0000, // 32 bits
8 => 0x0200, // 64 bits
_ => 0x0300, // default, 80 bits
};
// Get the original value of the control word to restore it later, when the
// `FPUControlWord` structure is dropped
// SAFETY: the `fnstcw` instruction has been audited to be able to work
// correctly with any `u16`
unsafe {
asm!(
"fnstcw word ptr [{}]",
in(reg) &mut cw,
options(nostack),
)
}
// Set the control word to the desired precision. This is achieved by masking
// away the old precision (bits 8 and 9, 0x300) and replacing it with
// the precision flag computed above.
set_cw((cw & 0xFCFF) | cw_precision);
FPUControlWord(cw)
}
impl Drop for FPUControlWord {
fn drop(&mut self) {
set_cw(self.0)
}
}
}
// In most architectures, floating point operations have an explicit bit size,
// therefore the precision of the computation is determined on a per-operation
// basis.
#[cfg(any(not(target_arch = "x86"), target_feature = "sse2"))]
mod fpu_precision {
#[inline]
pub const fn set_precision<T>() {
}
}