Line | Count | Source |
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 | ||
11 | pub 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")))] | |
20 | mod 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"))] | |
103 | mod fpu_precision { | |
104 | #[inline] | |
105 | 5.79k | pub const fn set_precision<T>() { |
106 | 5.79k | } Unexecuted instantiation: lexical_parse_float::fpu::fpu_precision::set_precision::<_> |
107 | } |