relibc: implement _fenv POSIX functions (fenv.h)

Replace 11 unimplemented!() stubs in src/header/_fenv/mod.rs with
real x86 MXCSR + x87 FPU control word implementations:

  feclearexcept, fegetexceptflag, feraiseexcept, fesetexceptflag,
  fetestexcept — exception flag management via stmxcsr/ldmxcsr
  fegetround, fesetround — rounding mode via MXCSR bits 13-14
  fegetenv, feholdexcept, fesetenv, feupdateenv — full FP environment
    save/restore via stmxcsr + fnstcw / ldmxcsr + fldcw

Constants corrected to match MXCSR bit positions:
  FE_INVALID=0x01, FE_DIVBYZERO=0x04, FE_OVERFLOW=0x08,
  FE_UNDERFLOW=0x10, FE_INEXACT=0x20, FE_ALL_EXCEPT=0x3D
  FE_TONEAREST=0, FE_DOWNWARD=1, FE_UPWARD=2, FE_TOWARDZERO=3

Types corrected to match x86-64 ABI:
  fexcept_t: u32 (MXCSR subset)
  fenv_t: struct { x87_cw: u16, _reserved: u16, mxcsr: u32 }

Non-x86 architectures fall through to no-op implementations
(returning 0 / FE_TONEAREST) guarded by cfg attributes.

This is part of the Round 4 stub sweep. The remaining relibc
unimplemented!() instances are mostly in commented-out code
(functions awaiting locale_t support) or are complex POSIX
functions requiring further implementation work.
This commit is contained in:
Red Bear OS
2026-07-26 21:22:07 +09:00
parent 3c5dc9d581
commit e5419e4491
+223 -20
View File
@@ -1,85 +1,288 @@
//! `fenv.h` implementation.
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
//!
//! On x86/x86_64 the floating-point environment is split between the
//! x87 FPU control word (`fnstcw`/`fldcw`, 16-bit) and the SSE MXCSR
//! register (`stmxcsr`/`ldmxcsr`, 32-bit). The POSIX `FE_*` exception
//! constants are defined to match the MXCSR exception flag bits so
//! that `feclearexcept`/`fetestexcept` can operate via direct bitwise
//! AND/OR on a stored MXCSR value.
//!
//! Rounding mode uses MXCSR bits 13-14 (0=nearest, 1=down, 2=up,
//! 3=toward-zero), matching the POSIX `FE_TONEAREST`/`FE_DOWNWARD`/
//! `FE_UPWARD`/`FE_TOWARDZERO` values directly.
use crate::platform::types::{c_int, c_ulonglong};
use crate::platform::types::{c_int, c_uint};
// --- Exception flag constants (match MXCSR bit positions) ---
pub const FE_INVALID: c_int = 0x01;
pub const FE_DIVBYZERO: c_int = 0x04;
pub const FE_OVERFLOW: c_int = 0x08;
pub const FE_UNDERFLOW: c_int = 0x10;
pub const FE_INEXACT: c_int = 0x20;
pub const FE_ALL_EXCEPT: c_int =
FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT;
// --- Rounding mode constants (match MXCSR bits 13-14) ---
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
pub const FE_ALL_EXCEPT: c_int = 0;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
pub const FE_TONEAREST: c_int = 0;
pub const FE_DOWNWARD: c_int = 1;
pub const FE_UPWARD: c_int = 2;
pub const FE_TOWARDZERO: c_int = 3;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
pub type fexcept_t = c_ulonglong;
/// Stored FP exception flags. On x86 this is a subset of the MXCSR
/// register containing the exception flag bits (bits 0-5).
pub type fexcept_t = c_uint;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fenv.h.html>.
/// Complete floating-point environment: x87 control word + SSE MXCSR.
/// The layout matches the structure used by `fegetenv`/`fesetenv` on
/// x86-64 Linux (glibc's `fenv_t` union stores the same two fields).
#[repr(C)]
pub struct fenv_t {
pub cw: c_ulonglong,
pub x87_cw: u16,
pub _reserved0: u16,
pub mxcsr: u32,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn read_mxcsr() -> u32 {
let mut mxcsr: u32 = 0;
unsafe {
core::arch::asm!(
"stmxcsr [{addr}]",
addr = in(reg) core::ptr::addr_of_mut!(mxcsr),
options(nostack, preserves_flags),
);
}
mxcsr
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn write_mxcsr(mxcsr: u32) {
unsafe {
core::arch::asm!(
"ldmxcsr [{addr}]",
addr = in(reg) core::ptr::addr_of!(mxcsr),
options(nostack, preserves_flags),
);
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn read_x87_cw() -> u16 {
let mut cw: u16 = 0;
unsafe {
core::arch::asm!(
"fnstcw [{addr}]",
addr = in(reg) core::ptr::addr_of_mut!(cw),
options(nostack, preserves_flags),
);
}
cw
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn write_x87_cw(cw: u16) {
unsafe {
core::arch::asm!(
"fldcw [{addr}]",
addr = in(reg) core::ptr::addr_of!(cw),
options(nostack, preserves_flags),
);
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feclearexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feclearexcept(excepts: c_int) -> c_int {
unimplemented!();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let masked = excepts & FE_ALL_EXCEPT;
if masked != 0 {
let mxcsr = read_mxcsr();
write_mxcsr(mxcsr & !masked as u32);
}
}
let _ = excepts;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetenv.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fegetenv(envp: *mut fenv_t) -> c_int {
unimplemented!();
if envp.is_null() {
return -1;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
(*envp).x87_cw = read_x87_cw();
(*envp)._reserved0 = 0;
(*envp).mxcsr = read_mxcsr();
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
unsafe {
*envp = core::mem::zeroed();
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetexceptflag.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fegetexceptflag(flagp: *mut fexcept_t, excepts: c_int) -> c_int {
unimplemented!();
if flagp.is_null() {
return -1;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
let mxcsr = read_mxcsr();
*flagp = (mxcsr as c_int & excepts & FE_ALL_EXCEPT) as c_uint;
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
unsafe {
*flagp = 0;
}
let _ = excepts;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetround.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fegetround() -> c_int {
FE_TONEAREST
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let mxcsr = read_mxcsr();
((mxcsr >> 13) & 0x3) as c_int
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
FE_TONEAREST
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feholdexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feholdexcept(envp: *mut fenv_t) -> c_int {
unimplemented!();
if envp.is_null() {
return -1;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
// Save current environment.
(*envp).x87_cw = read_x87_cw();
(*envp)._reserved0 = 0;
(*envp).mxcsr = read_mxcsr();
// Clear all exception flags and mask all exceptions (non-stop mode).
let mxcsr = read_mxcsr();
write_mxcsr((mxcsr | FE_ALL_EXCEPT as u32 | (FE_ALL_EXCEPT as u32) << 7) & !(FE_ALL_EXCEPT as u32));
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
unsafe {
*envp = core::mem::zeroed();
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feraiseexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feraiseexcept(except: c_int) -> c_int {
unimplemented!();
pub unsafe extern "C" fn feraiseexcept(excepts: c_int) -> c_int {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let masked = excepts & FE_ALL_EXCEPT;
if masked != 0 {
let mxcsr = read_mxcsr();
// Set the exception flag bits. Unmask them first so the
// exception is actually raised (not just flagged).
write_mxcsr((mxcsr & !((masked as u32) << 7)) | masked as u32);
}
}
let _ = excepts;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fesetenv.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fesetenv(envp: *const fenv_t) -> c_int {
unimplemented!();
if envp.is_null() {
return -1;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
write_x87_cw((*envp).x87_cw);
write_mxcsr((*envp).mxcsr);
}
let _ = envp;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fesetexceptflag.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fesetexceptflag(flagp: *const fexcept_t, excepts: c_int) -> c_int {
unimplemented!();
if flagp.is_null() {
return -1;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
let masked = excepts & FE_ALL_EXCEPT;
if masked != 0 {
let mxcsr = read_mxcsr();
let new_flags = (*flagp as c_int) & masked;
write_mxcsr((mxcsr & !(masked as u32)) | new_flags as u32);
}
}
let _ = (flagp, excepts);
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fegetround.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fesetround(round: c_int) -> c_int {
unimplemented!();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if !(0..=3).contains(&round) {
return -1;
}
let mxcsr = read_mxcsr();
write_mxcsr((mxcsr & !(0x3 << 13)) | ((round as u32) << 13));
}
let _ = round;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fetestexcept.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn fetestexcept(excepts: c_int) -> c_int {
unimplemented!();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let mxcsr = read_mxcsr();
(mxcsr as c_int) & excepts & FE_ALL_EXCEPT
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
let _ = excepts;
0
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/feupdateenv.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn feupdateenv(envp: *const fenv_t) -> c_int {
unimplemented!();
if envp.is_null() {
return -1;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
// POSIX: save current exceptions, restore the environment, then
// re-raise the saved exceptions.
let current = read_mxcsr() as c_int & FE_ALL_EXCEPT;
write_x87_cw((*envp).x87_cw);
write_mxcsr((*envp).mxcsr);
if current != 0 {
let mxcsr = read_mxcsr();
write_mxcsr(mxcsr | current as u32);
}
}
let _ = envp;
0
}