Goals for Rust libm

Use cases for libm

  • WASM
  • libcore (systems without a libm, or where a libm is not linked)
  • Replacement for the system’s math library (e.g. for all binaries, including C ones)  
  • Platforms with widely-different floating-point math hardware support (w/o FPU, SIMD, etc.)
  • Tasks with varying accuracy requirements

WASM


There is no libm implementation for WASM, and WASM has many “virtual ISA” instructions for floating-point math, like f32.sqrt. The libm crate should work for WASM and lower to those.

libcore


Some systems do not have a libm, or for some applications linking the system’s libm is not desired. For this reason, the f32 and f64 types do not provide most methods when libstd is not available. libstd extends these types using the f32_runtime and f64_runtime lang-items to violate coherence (https://github.com/rust-lang/rust/blob/master/src/libstd/f32.rs#L29). 

libm should be usable for #[no_std] applications as an external crate, and provide a libstd-compatible API for f32 and f64 that can be drop-in as is. 

Vector types

Packed SIMD vector types are available for libcore for specific hardware (e.g. __m128), but the intent is to extend this at some point with portable packed SIMD vector types (e.g. f32x4). These require us to link against a “vector”-libm library called libmvec, and available in glibc, etc. 

There is a circular dependency between libm and libmvec, where libm benefits from calling vectorized versions of some operations in the implementation of the scalar algorithms, and libmvec calls scalar versions of the operations in some vector algorithms. Sleef and SVML solve this by bundling both into the same library. glibc has two dynamic libraries, where circular dependencies are allowed.

@burbull has a WIP port of sleef to Rust, here: https://github.com/burrbull/sleef-rs

  • Note: Sleef is a math library proposed for inclusion in the LLVM project - LLVM has currently native support for overriding libm with Intel SVML, and there are patches for Sleef - don’t know what happened with those.

Replacement for the system’s math library


In most platforms, binaries are linked statically or dynamically against the system’s math library. The API of this library is exposed to C via the <math.h> header file. Applications written in all programming languages can use their C FFI facilities to use this library (obviously C can use it natively). The ABI of this library is often part of the platforms ABI, and it is common for users to dynamically replace their libm implementation, e.g., with a newer version, or a version by a different vendor (e.g. Intel). 

Requirements to satisfy this goal

Be ABI compatible with the system’s libm, compile to a cdylib 

Pros of satisfying this goal

  • Rust could emit llvm.sqrtf-like math intrinsics, that LLVM can reason about, and these are then translated to calls to Rust’s libm only after optimizations. 
  • This allows constant folding (sqrt(0.0)0.0), vectorization (many consecutive sqrt calls, e.g., in a loop, into a single sqrt(simd_vector) call), etc.
  • Better testing: we can just link our library to any C math testing suite, and run it. 
  • Smaller compile-times: ship it with rustup and dynamically link it, instead of requiring users to compile it.
  • Better optimizations: extern "C" fn foo(…) → … Rust functions are nounwind (they are assumed to never panic, and panicking is UB). 

Cons of satisfying this goal

  • Can’t use Rust features in the API: e.g. we can’t return → (i32, f32) tuples in some functions, but need to use &mut i32 as “output parameters” instead
  • cdylib requirements are strict: currently the F32Ext/F64Ext traits are in the same crate as the math APIs, and those aren’t C compatible. We might need to layer crates to solve this (e.g. one crate implementing the C ABI, and one crate adding the F32Ext-like traits on top)
  • Requires more testing: to verify that we are ABI compatible with C