Skip to main content

c/
fenv.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4#[cfg(target_arch = "x86_64")]
5pub type fexcept_t = u16;
6
7#[cfg(target_arch = "aarch64")]
8pub type fexcept_t = u32;
9
10#[cfg(target_arch = "riscv64")]
11pub type fexcept_t = u32;
12
13// x87 fnstenv/fldenv (28 bytes) + stmxcsr/ldmxcsr (4 bytes) = 32 bytes
14#[cfg(target_arch = "x86_64")]
15#[repr(C)]
16pub struct fenv_t {
17    __x87_env: [u8; 28],
18    __mxcsr: u32,
19}
20
21#[cfg(target_arch = "aarch64")]
22#[repr(C)]
23pub struct fenv_t {
24    __fpcr: u32,
25    __fpsr: u32,
26}
27
28#[cfg(target_arch = "riscv64")]
29pub type fenv_t = u32;
30
31#[cfg(target_arch = "x86_64")]
32const FE_DFL_ENV: *const fenv_t = -1isize as *const fenv_t;
33#[cfg(target_arch = "aarch64")]
34const FE_DFL_ENV: *const fenv_t = -1isize as *const fenv_t;
35#[cfg(target_arch = "riscv64")]
36const FE_DFL_ENV: *const fenv_t = -1isize as *const fenv_t;
37
38#[cfg(target_arch = "x86_64")]
39mod feconst {
40    use super::*;
41    pub const FE_INVALID: c_int = 1;
42    pub const FE_DIVBYZERO: c_int = 4;
43    pub const FE_OVERFLOW: c_int = 8;
44    pub const FE_UNDERFLOW: c_int = 16;
45    pub const FE_INEXACT: c_int = 32;
46    pub const FE_ALL_EXCEPT: c_int = 63;
47
48    pub const FE_TONEAREST: c_int = 0;
49    pub const FE_DOWNWARD: c_int = 0x400;
50    pub const FE_UPWARD: c_int = 0x800;
51    pub const FE_TOWARDZERO: c_int = 0xc00;
52}
53
54#[cfg(target_arch = "aarch64")]
55mod feconst {
56    use super::*;
57    pub const FE_INVALID: c_int = 1;
58    pub const FE_DIVBYZERO: c_int = 2;
59    pub const FE_OVERFLOW: c_int = 4;
60    pub const FE_UNDERFLOW: c_int = 8;
61    pub const FE_INEXACT: c_int = 16;
62    pub const FE_ALL_EXCEPT: c_int = 31;
63
64    pub const FE_TONEAREST: c_int = 0;
65    pub const FE_DOWNWARD: c_int = 0x800000;
66    pub const FE_UPWARD: c_int = 0x400000;
67    pub const FE_TOWARDZERO: c_int = 0xc00000;
68}
69
70#[cfg(target_arch = "riscv64")]
71mod feconst {
72    use super::*;
73    pub const FE_INVALID: c_int = 16;
74    pub const FE_DIVBYZERO: c_int = 8;
75    pub const FE_OVERFLOW: c_int = 4;
76    pub const FE_UNDERFLOW: c_int = 2;
77    pub const FE_INEXACT: c_int = 1;
78    pub const FE_ALL_EXCEPT: c_int = 31;
79
80    pub const FE_TONEAREST: c_int = 0;
81    pub const FE_DOWNWARD: c_int = 2;
82    pub const FE_UPWARD: c_int = 3;
83    pub const FE_TOWARDZERO: c_int = 1;
84}
85
86use feconst::*;
87
88// x86_64 implementation using x87/MXCSR
89#[cfg(target_arch = "x86_64")]
90mod x86_64_imp {
91    use super::*;
92
93    // CW=0x037f (all masked, nearest, extended), tags=0xffff (all empty)
94    const DEFAULT_X87_ENV: [u8; 28] = {
95        let mut env = [0u8; 28];
96        env[0] = 0x7f;
97        env[1] = 0x03;
98        env[8] = 0xff;
99        env[9] = 0xff;
100        env
101    };
102    const DEFAULT_MXCSR: u32 = 0x1f80;
103
104    // musl semantics: clears x87, merges old x87 into MXCSR then clears MXCSR
105    #[no_mangle]
106    pub unsafe extern "C" fn feclearexcept(excepts: c_int) -> c_int {
107        let mask = excepts & 0x3f;
108        let sw: u16;
109        core::arch::asm!("fnstsw ax", out("ax") sw);
110        if (sw as c_int) & mask != 0 {
111            core::arch::asm!("fnclex");
112        }
113        let mut mxcsr: u32 = 0;
114        let p = &mut mxcsr as *mut u32;
115        core::arch::asm!("stmxcsr [{p}]", p = in(reg) p);
116        let mxcsr_val = core::ptr::read(p) | ((sw as u32) & 0x3f);
117        if mxcsr_val & (mask as u32) != 0 {
118            core::ptr::write(p, mxcsr_val & !(mask as u32));
119            core::arch::asm!("ldmxcsr [{p}]", p = in(reg) p);
120        }
121        0
122    }
123
124    #[no_mangle]
125    pub unsafe extern "C" fn feraiseexcept(excepts: c_int) -> c_int {
126        let mask = (excepts & 0x3f) as u32;
127        let mut mxcsr: u32 = 0;
128        let p = &mut mxcsr as *mut u32;
129        core::arch::asm!("stmxcsr [{p}]", p = in(reg) p);
130        core::ptr::write(p, core::ptr::read(p) | mask);
131        core::arch::asm!("ldmxcsr [{p}]", p = in(reg) p);
132        0
133    }
134
135    #[no_mangle]
136    pub unsafe extern "C" fn fetestexcept(excepts: c_int) -> c_int {
137        let mask = excepts & 0x3f;
138        let mut mxcsr: u32 = 0;
139        let p = &mut mxcsr as *mut u32;
140        core::arch::asm!("stmxcsr [{p}]", p = in(reg) p);
141        let sw: u16;
142        core::arch::asm!("fnstsw ax", out("ax") sw);
143        ((sw as c_int) | (core::ptr::read(p) as c_int)) & mask
144    }
145
146    #[no_mangle]
147    pub unsafe extern "C" fn fegetround() -> c_int {
148        let mut mxcsr: u32 = 0;
149        let p = &mut mxcsr as *mut u32;
150        core::arch::asm!("stmxcsr [{p}]", p = in(reg) p);
151        // MXCSR bits 13-14 map to x87 CW bits 10-11
152        ((core::ptr::read(p) >> 3) & 0xc00) as c_int
153    }
154
155    #[no_mangle]
156    pub unsafe extern "C" fn fesetround(r: c_int) -> c_int {
157        if r != FE_TONEAREST && r != FE_DOWNWARD && r != FE_UPWARD && r != FE_TOWARDZERO {
158            return -1;
159        }
160        let mut cw: u16 = 0;
161        let p_cw = &mut cw as *mut u16;
162        core::arch::asm!("fnstcw [{p}]", p = in(reg) p_cw);
163        core::ptr::write(p_cw, (core::ptr::read(p_cw) & 0xf3ff) | (r as u16 & 0x0c00));
164        core::arch::asm!("fldcw [{p}]", p = in(reg) p_cw);
165
166        let mut mxcsr: u32 = 0;
167        let p_mx = &mut mxcsr as *mut u32;
168        core::arch::asm!("stmxcsr [{p}]", p = in(reg) p_mx);
169        core::ptr::write(p_mx, (core::ptr::read(p_mx) & 0xffff9fff) | ((r as u32 & 0xc00) << 3));
170        core::arch::asm!("ldmxcsr [{p}]", p = in(reg) p_mx);
171        0
172    }
173
174    #[no_mangle]
175    pub unsafe extern "C" fn fegetenv(envp: *mut fenv_t) -> c_int {
176        core::arch::asm!("fnstenv [{p}]", p = in(reg) envp);
177        let mxcsr_p = (envp as *mut u8).add(28) as *mut u32;
178        core::arch::asm!("stmxcsr [{p}]", p = in(reg) mxcsr_p);
179        0
180    }
181
182    #[no_mangle]
183    pub unsafe extern "C" fn fesetenv(envp: *const fenv_t) -> c_int {
184        if envp == FE_DFL_ENV {
185            core::arch::asm!("fldenv [{p}]", p = in(reg) DEFAULT_X87_ENV.as_ptr());
186            let p = &DEFAULT_MXCSR as *const u32;
187            core::arch::asm!("ldmxcsr [{p}]", p = in(reg) p);
188        } else {
189            core::arch::asm!("fldenv [{p}]", p = in(reg) envp);
190            let mxcsr_p = (envp as *const u8).add(28) as *const u32;
191            core::arch::asm!("ldmxcsr [{p}]", p = in(reg) mxcsr_p);
192        }
193        0
194    }
195}
196
197#[cfg(target_arch = "x86_64")]
198use x86_64_imp::*;
199
200// aarch64 implementation using FPCR/FPSR
201#[cfg(target_arch = "aarch64")]
202mod aarch64_imp {
203    use super::*;
204
205    #[no_mangle]
206    pub unsafe extern "C" fn feclearexcept(excepts: c_int) -> c_int {
207        let mask = (excepts & 0x1f) as u64;
208        let mut fpsr: u64;
209        core::arch::asm!("mrs {fpsr}, fpsr", fpsr = out(reg) fpsr);
210        let new_fpsr = fpsr & !mask;
211        core::arch::asm!("msr fpsr, {fpsr}", fpsr = in(reg) new_fpsr);
212        0
213    }
214
215    #[no_mangle]
216    pub unsafe extern "C" fn feraiseexcept(excepts: c_int) -> c_int {
217        let mask = (excepts & 0x1f) as u64;
218        let mut fpsr: u64;
219        core::arch::asm!("mrs {fpsr}, fpsr", fpsr = out(reg) fpsr);
220        let new_fpsr = fpsr | mask;
221        core::arch::asm!("msr fpsr, {fpsr}", fpsr = in(reg) new_fpsr);
222        0
223    }
224
225    #[no_mangle]
226    pub unsafe extern "C" fn fetestexcept(excepts: c_int) -> c_int {
227        let mask = (excepts & 0x1f) as u64;
228        let mut fpsr: u64;
229        core::arch::asm!("mrs {fpsr}, fpsr", fpsr = out(reg) fpsr);
230        (fpsr & mask) as c_int
231    }
232
233    #[no_mangle]
234    pub unsafe extern "C" fn fegetround() -> c_int {
235        let mut fpcr: u64;
236        core::arch::asm!("mrs {fpcr}, fpcr", fpcr = out(reg) fpcr);
237        (fpcr as c_int) & 0xc00000
238    }
239
240    #[no_mangle]
241    pub unsafe extern "C" fn fesetround(r: c_int) -> c_int {
242        if r != FE_TONEAREST && r != FE_DOWNWARD && r != FE_UPWARD && r != FE_TOWARDZERO {
243            return -1;
244        }
245        let mut fpcr: u64;
246        core::arch::asm!("mrs {fpcr}, fpcr", fpcr = out(reg) fpcr);
247        let new_fpcr = (fpcr & !0xc00000u64) | ((r as u64) & 0xc00000);
248        core::arch::asm!("msr fpcr, {fpcr}", fpcr = in(reg) new_fpcr);
249        0
250    }
251
252    #[no_mangle]
253    pub unsafe extern "C" fn fegetenv(envp: *mut fenv_t) -> c_int {
254        let mut fpcr: u64;
255        let mut fpsr: u64;
256        core::arch::asm!("mrs {fpcr}, fpcr", fpcr = out(reg) fpcr);
257        core::arch::asm!("mrs {fpsr}, fpsr", fpsr = out(reg) fpsr);
258        (*envp).__fpcr = fpcr as u32;
259        (*envp).__fpsr = fpsr as u32;
260        0
261    }
262
263    #[no_mangle]
264    pub unsafe extern "C" fn fesetenv(envp: *const fenv_t) -> c_int {
265        let (fpcr, fpsr) = if envp == FE_DFL_ENV {
266            (0u64, 0u64)
267        } else {
268            ((*envp).__fpcr as u64, (*envp).__fpsr as u64)
269        };
270        core::arch::asm!("msr fpcr, {fpcr}", fpcr = in(reg) fpcr);
271        core::arch::asm!("msr fpsr, {fpsr}", fpsr = in(reg) fpsr);
272        0
273    }
274}
275
276#[cfg(target_arch = "aarch64")]
277use aarch64_imp::*;
278
279// riscv64 implementation using fflags/frm/fcsr CSRs
280#[cfg(target_arch = "riscv64")]
281mod riscv64_imp {
282    use super::*;
283
284    #[no_mangle]
285    pub unsafe extern "C" fn feclearexcept(excepts: c_int) -> c_int {
286        let mask = (excepts & 0x1f) as u64;
287        // csrc fflags, a0 — clear bits in fflags
288        core::arch::asm!(
289            "csrc fflags, {mask}",
290            mask = in(reg) mask,
291            options(nostack),
292        );
293        0
294    }
295
296    #[no_mangle]
297    pub unsafe extern "C" fn feraiseexcept(excepts: c_int) -> c_int {
298        let mask = (excepts & 0x1f) as u64;
299        // csrs fflags, a0 — set bits in fflags
300        core::arch::asm!(
301            "csrs fflags, {mask}",
302            mask = in(reg) mask,
303            options(nostack),
304        );
305        0
306    }
307
308    #[no_mangle]
309    pub unsafe extern "C" fn fetestexcept(excepts: c_int) -> c_int {
310        let fflags: u64;
311        // frflags t0 — read fflags
312        core::arch::asm!(
313            "frflags {fflags}",
314            fflags = out(reg) fflags,
315            options(nostack),
316        );
317        (fflags as c_int) & (excepts & 0x1f)
318    }
319
320    #[no_mangle]
321    pub unsafe extern "C" fn fegetround() -> c_int {
322        let frm: u64;
323        // frrm a0 — read rounding mode
324        core::arch::asm!(
325            "frrm {frm}",
326            frm = out(reg) frm,
327            options(nostack),
328        );
329        frm as c_int
330    }
331
332    #[no_mangle]
333    pub unsafe extern "C" fn fesetround(r: c_int) -> c_int {
334        if r != FE_TONEAREST && r != FE_DOWNWARD && r != FE_UPWARD && r != FE_TOWARDZERO {
335            return -1;
336        }
337        // fsrm t0, a0 — set rounding mode
338        core::arch::asm!(
339            "fsrm zero, {r}",
340            r = in(reg) r as u64,
341            options(nostack),
342        );
343        0
344    }
345
346    #[no_mangle]
347    pub unsafe extern "C" fn fegetenv(envp: *mut fenv_t) -> c_int {
348        let fcsr: u64;
349        // frcsr t0 — read full CSR
350        core::arch::asm!(
351            "frcsr {fcsr}",
352            fcsr = out(reg) fcsr,
353            options(nostack),
354        );
355        *envp = fcsr as u32;
356        0
357    }
358
359    #[no_mangle]
360    pub unsafe extern "C" fn fesetenv(envp: *const fenv_t) -> c_int {
361        let val = if envp == FE_DFL_ENV {
362            0u64
363        } else {
364            *envp as u64
365        };
366        // fscsr t1 — set full CSR
367        core::arch::asm!(
368            "fscsr zero, {val}",
369            val = in(reg) val,
370            options(nostack),
371        );
372        0
373    }
374}
375
376#[cfg(target_arch = "riscv64")]
377use riscv64_imp::*;
378
379#[no_mangle]
380pub unsafe extern "C" fn fegetexceptflag(fp: *mut fexcept_t, mask: c_int) -> c_int {
381    *fp = fetestexcept(mask) as fexcept_t;
382    0
383}
384
385#[no_mangle]
386pub unsafe extern "C" fn fesetexceptflag(fp: *const fexcept_t, mask: c_int) -> c_int {
387    let flags = *fp as c_int;
388    feclearexcept((!flags) & mask);
389    feraiseexcept(flags & mask);
390    0
391}
392
393#[no_mangle]
394pub unsafe extern "C" fn feholdexcept(envp: *mut fenv_t) -> c_int {
395    fegetenv(envp);
396    feclearexcept(FE_ALL_EXCEPT);
397    0
398}
399
400#[no_mangle]
401pub unsafe extern "C" fn feupdateenv(envp: *const fenv_t) -> c_int {
402    let ex = fetestexcept(FE_ALL_EXCEPT);
403    fesetenv(envp);
404    feraiseexcept(ex);
405    0
406}
407
408// C99 FLT_ROUNDS: 0=towardzero, 1=nearest, 2=upward, 3=downward
409#[no_mangle]
410pub extern "C" fn __flt_rounds() -> c_int {
411    match unsafe { fegetround() } {
412        FE_TOWARDZERO => 0,
413        FE_TONEAREST => 1,
414        FE_UPWARD => 2,
415        FE_DOWNWARD => 3,
416        _ => -1,
417    }
418}