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