Introduce a op module with struct representations of each routine

This contains:

1. Per-function and per-operation enums created by the proc macro
2. The `MathOp` trait which is implemented once per struct representing
   a function
3. Submodules for each function, each containing a `Routine` struct that
   implements `MathOp`
This commit is contained in:
Trevor Gross
2024-10-31 02:45:37 -05:00
parent 5b0a775c12
commit 7db74d78e8
3 changed files with 115 additions and 2 deletions

View File

@@ -1,10 +1,12 @@
pub mod gen;
#[cfg(feature = "test-multiprecision")]
pub mod mpfloat;
pub mod op;
mod precision;
mod test_traits;
pub use libm::support::{Float, Int};
pub use op::{BaseName, MathOp, Name};
pub use precision::{MaybeOverride, SpecialCase, multiprec_allowed_ulp, musl_allowed_ulp};
pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, Hex, TupleCall};

View File

@@ -0,0 +1,111 @@
//! Types representing individual functions.
//!
//! Each routine gets a module with its name, e.g. `mod sinf { /* ... */ }`. The module
//! contains a unit struct `Routine` which implements `MathOp`.
//!
//! Basically everything could be called a "function" here, so we loosely use the following
//! terminology:
//!
//! - "Function": the math operation that does not have an associated precision. E.g. `f(x) = e^x`,
//! `f(x) = log(x)`.
//! - "Routine": A code implementation of a math operation with a specific precision. E.g. `exp`,
//! `expf`, `expl`, `log`, `logf`.
//! - "Operation" / "Op": Something that relates a routine to a function or is otherwise higher
//! level. `Op` is also used as the name for generic parameters since it is terse.
use crate::{CheckOutput, Float, TupleCall};
/// An enum representing each possible routine name.
#[libm_macros::function_enum(BaseName)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Name {}
/// The name without any type specifier, e.g. `sin` and `sinf` both become `sin`.
#[libm_macros::base_name_enum]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BaseName {}
/// Attributes ascribed to a `libm` routine including signature, type information,
/// and naming.
pub trait MathOp {
/// The float type used for this operation.
type FTy: Float;
/// The function type representing the signature in a C library.
type CFn: Copy;
/// Arguments passed to the C library function as a tuple. These may include `&mut` return
/// values.
type CArgs<'a>
where
Self: 'a;
/// The type returned by C implementations.
type CRet;
/// The signature of the Rust function as a `fn(...) -> ...` type.
type RustFn: Copy;
/// Arguments passed to the Rust library function as a tuple.
///
/// The required `TupleCall` bounds ensure this type can be passed either to the C function or
/// to the Rust function.
type RustArgs: Copy
+ TupleCall<Self::RustFn, Output = Self::RustRet>
+ TupleCall<Self::CFn, Output = Self::RustRet>;
/// Type returned from the Rust function.
type RustRet: CheckOutput<Self::RustArgs>;
/// The name of this function, including suffix (e.g. `sin`, `sinf`).
const NAME: Name;
/// The name as a string.
const NAME_STR: &'static str = Self::NAME.as_str();
/// The name of the function excluding the type suffix, e.g. `sin` and `sinf` are both `sin`.
const BASE_NAME: BaseName = Self::NAME.base_name();
/// The function in `libm` which can be called.
const ROUTINE: Self::RustFn;
}
macro_rules! do_thing {
// Matcher for unary functions
(
fn_name: $fn_name:ident,
FTy: $FTy:ty,
CFn: $CFn:ty,
CArgs: $CArgs:ty,
CRet: $CRet:ty,
RustFn: $RustFn:ty,
RustArgs: $RustArgs:ty,
RustRet: $RustRet:ty,
) => {
paste::paste! {
pub mod $fn_name {
use super::*;
pub struct Routine;
impl MathOp for Routine {
type FTy = $FTy;
type CFn = for<'a> $CFn;
type CArgs<'a> = $CArgs where Self: 'a;
type CRet = $CRet;
type RustFn = $RustFn;
type RustArgs = $RustArgs;
type RustRet = $RustRet;
const NAME: Name = Name::[< $fn_name:camel >];
const ROUTINE: Self::RustFn = libm::$fn_name;
}
}
}
};
}
libm_macros::for_each_function! {
callback: do_thing,
emit_types: all,
}

View File

@@ -137,7 +137,7 @@ where
}
}
impl<T1, T2, T3> TupleCall<fn(T1, &mut T2, &mut T3)> for (T1,)
impl<T1, T2, T3> TupleCall<for<'a> fn(T1, &'a mut T2, &'a mut T3)> for (T1,)
where
T1: fmt::Debug,
T2: fmt::Debug + Default,
@@ -145,7 +145,7 @@ where
{
type Output = (T2, T3);
fn call(self, f: fn(T1, &mut T2, &mut T3)) -> Self::Output {
fn call(self, f: for<'a> fn(T1, &'a mut T2, &'a mut T3)) -> Self::Output {
let mut t2 = T2::default();
let mut t3 = T3::default();
f(self.0, &mut t2, &mut t3);