Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ _bigint = ["dep:num-traits", "dep:num-integer"] # Internal feature. User must u
# See also: https://github.com/python/cpython/issues/132763
mul_add = []

# For WASM and other targets without native libm
[target.'cfg(not(any(unix, windows)))'.dependencies]
libm = "0.2"

[dependencies]
libc = "0.2"
num-complex = { version = "0.4", optional = true }
Expand Down
10 changes: 8 additions & 2 deletions src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ pub(crate) fn set_errno(value: i32) {
unsafe {
*_errno() = value;
}
#[cfg(all(unix, not(any(target_os = "linux", target_os = "android", target_os = "macos"))))]
#[cfg(all(
unix,
not(any(target_os = "linux", target_os = "android", target_os = "macos"))
))]
unsafe {
// FreeBSD, NetBSD, OpenBSD, etc. use __error()
*libc::__error() = value;
Expand Down Expand Up @@ -73,7 +76,10 @@ pub(crate) fn get_errno() -> i32 {
unsafe {
*_errno()
}
#[cfg(all(unix, not(any(target_os = "linux", target_os = "android", target_os = "macos"))))]
#[cfg(all(
unix,
not(any(target_os = "linux", target_os = "android", target_os = "macos"))
))]
unsafe {
// FreeBSD, NetBSD, OpenBSD, etc. use __error()
*libc::__error()
Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ pub mod math;

// Internal modules
mod err;
// Native libm via FFI (unix/windows)
#[cfg(any(unix, windows))]
pub(crate) mod m;
// Pure Rust libm for WASM and other targets
#[cfg(not(any(unix, windows)))]
#[path = "m_rust.rs"]
pub(crate) mod m;
#[cfg(any(unix, windows))]
mod m_sys;
#[cfg(test)]
mod test;
Expand Down
211 changes: 211 additions & 0 deletions src/m_rust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
//! Pure Rust implementations of libm functions using the libm crate.
//! Used on targets without native libm (e.g., WASM).

// Trigonometric functions

#[inline(always)]
pub fn acos(n: f64) -> f64 {
libm::acos(n)
}

#[inline(always)]
pub fn asin(n: f64) -> f64 {
libm::asin(n)
}

#[inline(always)]
pub fn atan(n: f64) -> f64 {
libm::atan(n)
}

#[inline(always)]
pub fn atan2(y: f64, x: f64) -> f64 {
libm::atan2(y, x)
}

#[inline(always)]
pub fn cos(n: f64) -> f64 {
libm::cos(n)
}

#[inline(always)]
pub fn sin(n: f64) -> f64 {
libm::sin(n)
}

#[inline(always)]
pub fn tan(n: f64) -> f64 {
libm::tan(n)
}

// Hyperbolic functions

#[inline(always)]
pub fn acosh(n: f64) -> f64 {
libm::acosh(n)
}

#[inline(always)]
pub fn asinh(n: f64) -> f64 {
libm::asinh(n)
}

#[inline(always)]
pub fn atanh(n: f64) -> f64 {
libm::atanh(n)
}

#[inline(always)]
pub fn cosh(n: f64) -> f64 {
libm::cosh(n)
}

#[inline(always)]
pub fn sinh(n: f64) -> f64 {
libm::sinh(n)
}

#[inline(always)]
pub fn tanh(n: f64) -> f64 {
libm::tanh(n)
}

// Exponential and logarithmic functions

#[inline(always)]
pub fn exp(n: f64) -> f64 {
libm::exp(n)
}

#[inline(always)]
pub fn exp2(n: f64) -> f64 {
libm::exp2(n)
}

#[inline(always)]
pub fn expm1(n: f64) -> f64 {
libm::expm1(n)
}

#[inline(always)]
pub fn log(n: f64) -> f64 {
libm::log(n)
}

#[inline(always)]
pub fn log10(n: f64) -> f64 {
libm::log10(n)
}

#[inline(always)]
pub fn log1p(n: f64) -> f64 {
libm::log1p(n)
}

#[inline(always)]
pub fn log2(n: f64) -> f64 {
libm::log2(n)
}

// Power functions

#[inline(always)]
pub fn cbrt(n: f64) -> f64 {
libm::cbrt(n)
}

#[inline(always)]
pub fn hypot(x: f64, y: f64) -> f64 {
libm::hypot(x, y)
}

#[inline(always)]
pub fn pow(x: f64, y: f64) -> f64 {
libm::pow(x, y)
}

#[inline(always)]
pub fn sqrt(n: f64) -> f64 {
libm::sqrt(n)
}

// Floating-point manipulation functions

#[inline(always)]
pub fn ceil(n: f64) -> f64 {
libm::ceil(n)
}

#[inline(always)]
pub fn copysign(x: f64, y: f64) -> f64 {
libm::copysign(x, y)
}

#[inline(always)]
pub fn fabs(n: f64) -> f64 {
libm::fabs(n)
}

#[inline(always)]
pub fn floor(n: f64) -> f64 {
libm::floor(n)
}

#[inline(always)]
pub fn fmod(x: f64, y: f64) -> f64 {
libm::fmod(x, y)
}

#[inline(always)]
pub fn frexp(n: f64, exp: &mut i32) -> f64 {
let (mantissa, exponent) = libm::frexp(n);
*exp = exponent;
mantissa
}

#[inline(always)]
pub fn ldexp(x: f64, n: i32) -> f64 {
libm::ldexp(x, n)
}

#[inline(always)]
pub fn modf(n: f64, iptr: &mut f64) -> f64 {
let (frac, int) = libm::modf(n);
*iptr = int;
frac
}
Comment on lines +159 to +176
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Signature mismatch: frexp and modf use references vs raw pointers.

The m_sys.rs FFI declarations use raw pointers (*mut i32, *mut f64), while these pure Rust wrappers use safe references (&mut i32, &mut f64). If callers use raw pointers (common in FFI-adjacent code), this will cause compilation errors on WASM.

Verify if existing call sites use references or raw pointers:

#!/bin/bash
# Check how frexp and modf are called in the codebase
rg -n '\bm::(frexp|modf)\s*\(' src/ -A2
🤖 Prompt for AI Agents
In @src/m_rust.rs around lines 159 - 176, The wrappers frexp and modf currently
take safe references (&mut i32, &mut f64) but the FFI declarations in m_sys.rs
expect raw pointers (*mut i32, *mut f64); change frexp to pub fn frexp(n: f64,
exp: *mut i32) -> f64 and modf to pub fn modf(n: f64, iptr: *mut f64) -> f64,
dereference the pointers inside an unsafe block to assign the returned
exponent/int (e.g., let (mantissa, exponent) = libm::frexp(n); unsafe { if
!exp.is_null() { *exp = exponent } } ), and similarly for modf, preserving
return values; ensure ldexp remains unchanged, and run the suggested rg search
to verify/update all call sites that pass raw pointers.


#[inline(always)]
pub fn nextafter(x: f64, y: f64) -> f64 {
libm::nextafter(x, y)
}

#[inline(always)]
pub fn remainder(x: f64, y: f64) -> f64 {
libm::remainder(x, y)
}

#[inline(always)]
pub fn trunc(n: f64) -> f64 {
libm::trunc(n)
}

// Special functions

#[inline(always)]
pub fn erf(n: f64) -> f64 {
libm::erf(n)
}

#[inline(always)]
pub fn erfc(n: f64) -> f64 {
libm::erfc(n)
}

// Platform-specific sincos (fallback: call sin and cos separately)

#[cfg(feature = "complex")]
#[inline(always)]
pub fn sincos(x: f64) -> (f64, f64) {
(libm::sin(x), libm::cos(x))
}
Comment on lines +207 to +211
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check for sincos declaration in m_sys.rs
grep -n "sincos" src/m_sys.rs

Repository: RustPython/pymath

Length of output: 178


🏁 Script executed:

# Check for m::sincos usage in cmath module
rg -n 'm::sincos' src/cmath/

Repository: RustPython/pymath

Length of output: 349


🏁 Script executed:

# Check the module structure to understand how m:: is resolved
cat -n src/lib.rs | head -80

Repository: RustPython/pymath

Length of output: 3360


🏁 Script executed:

# Look for any sincos declarations across the codebase
rg -n "fn sincos" src/ --type rs

Repository: RustPython/pymath

Length of output: 87


🏁 Script executed:

# Check if there's feature gating around sincos in m_rust.rs
rg -B 5 -A 5 "sincos" src/m_rust.rs

Repository: RustPython/pymath

Length of output: 316


🏁 Script executed:

# Get line count of m_sys.rs
wc -l src/m_sys.rs

Repository: RustPython/pymath

Length of output: 76


🏁 Script executed:

# Read m_sys.rs to see if sincos is missing or if there's feature gating
cat -n src/m_sys.rs

Repository: RustPython/pymath

Length of output: 2731


🏁 Script executed:

# Verify compilation by checking Cargo.toml features
cat src/../Cargo.toml | grep -A 20 "\[features\]"

Repository: RustPython/pymath

Length of output: 992


Add sincos FFI declaration to m_sys.rs.

The sincos function is defined in m_rust.rs (lines 207-211) with the complex feature gate, but m_sys.rs lacks the corresponding FFI binding. On unix/windows targets with the complex feature enabled (default), the cmath module calls m::sincos at 4 locations (trigonometric.rs:121, 167; misc.rs:72; exponential.rs:115), which would fail to compile because sincos is not declared in the system libm FFI. Add the FFI declaration: pub fn sincos(x: f64) -> (f64, f64); to match the fallback implementation in m_rust.rs and enable CPython-compatible behavior on unix/windows.

🤖 Prompt for AI Agents
In @src/m_rust.rs around lines 207 - 211, m_sys.rs is missing the FFI binding
for the sincos function defined under the complex feature in m_rust.rs, causing
unresolved calls to m::sincos from the cmath module; add the FFI declaration
matching the Rust fallback: declare pub fn sincos(x: f64) -> (f64, f64); inside
m_sys.rs (under the same cfg(feature = "complex")/platform-appropriate block) so
calls from trigonometric.rs, misc.rs and exponential.rs link correctly on
unix/windows when the complex feature is enabled.

38 changes: 4 additions & 34 deletions src/m_sys.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// These symbols are all defined by `libm`,
// or by `compiler-builtins` on unsupported platforms.
// Raw FFI declarations for system libm.
// Only available on unix/windows where native libm exists.

#[cfg(any(unix, windows))]
#[cfg_attr(unix, link(name = "m"))]
#[allow(dead_code)]
unsafe extern "C" {
Expand Down Expand Up @@ -45,7 +47,6 @@ unsafe extern "C" {
pub fn ceil(n: f64) -> f64;
pub fn copysign(x: f64, y: f64) -> f64;
pub fn fabs(n: f64) -> f64;
// pub fn fdim(a: f64, b: f64) -> f64;
pub fn fdimf(a: f32, b: f32) -> f32;
pub fn floor(n: f64) -> f64;
pub fn fmod(x: f64, y: f64) -> f64;
Expand All @@ -61,38 +62,7 @@ unsafe extern "C" {
pub fn erfc(n: f64) -> f64;
pub fn erff(n: f32) -> f32;
pub fn erfcf(n: f32) -> f32;
// pub fn lgamma_r(n: f64, s: &mut i32) -> f64;
#[cfg(not(target_os = "aix"))]
pub fn lgammaf_r(n: f32, s: &mut i32) -> f32;
// pub fn tgamma(n: f64) -> f64;
pub fn tgammaf(n: f32) -> f32;

// pub fn acosf128(n: f128) -> f128;
// pub fn asinf128(n: f128) -> f128;
// pub fn atanf128(n: f128) -> f128;
// pub fn atan2f128(a: f128, b: f128) -> f128;
// pub fn cbrtf128(n: f128) -> f128;
// pub fn coshf128(n: f128) -> f128;
// pub fn expm1f128(n: f128) -> f128;
// pub fn hypotf128(x: f128, y: f128) -> f128;
// pub fn log1pf128(n: f128) -> f128;
// pub fn sinhf128(n: f128) -> f128;
// pub fn tanf128(n: f128) -> f128;
// pub fn tanhf128(n: f128) -> f128;
// pub fn tgammaf128(n: f128) -> f128;
// pub fn lgammaf128_r(n: f128, s: &mut i32) -> f128;
// pub fn erff128(n: f128) -> f128;
// pub fn erfcf128(n: f128) -> f128;

// cfg_if::cfg_if! {
// if #[cfg(not(all(target_os = "windows", target_env = "msvc", target_arch = "x86")))] {
// pub fn acosf(n: f32) -> f32;
// pub fn asinf(n: f32) -> f32;
// pub fn atan2f(a: f32, b: f32) -> f32;
// pub fn atanf(n: f32) -> f32;
// pub fn coshf(n: f32) -> f32;
// pub fn sinhf(n: f32) -> f32;
// pub fn tanf(n: f32) -> f32;
// pub fn tanhf(n: f32) -> f32;
// }}
}