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