about summary refs log tree commit diff
path: root/library/compiler-builtins/crates/util/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/compiler-builtins/crates/util/src/main.rs')
-rw-r--r--library/compiler-builtins/crates/util/src/main.rs350
1 files changed, 350 insertions, 0 deletions
diff --git a/library/compiler-builtins/crates/util/src/main.rs b/library/compiler-builtins/crates/util/src/main.rs
new file mode 100644
index 00000000000..5972181531b
--- /dev/null
+++ b/library/compiler-builtins/crates/util/src/main.rs
@@ -0,0 +1,350 @@
+//! Helper CLI utility for common tasks.
+
+#![cfg_attr(f16_enabled, feature(f16))]
+#![cfg_attr(f128_enabled, feature(f128))]
+
+use std::any::type_name;
+use std::env;
+use std::num::ParseIntError;
+use std::str::FromStr;
+
+use libm::support::{Hexf, hf32, hf64};
+#[cfg(feature = "build-mpfr")]
+use libm_test::mpfloat::MpOp;
+use libm_test::{MathOp, TupleCall};
+#[cfg(feature = "build-mpfr")]
+use rug::az::{self, Az};
+
+const USAGE: &str = "\
+usage:
+
+cargo run -p util -- <SUBCOMMAND>
+
+SUBCOMMAND:
+    eval <BASIS> <OP> inputs...
+        Evaulate the expression with a given basis. This can be useful for
+        running routines with a debugger, or quickly checking input. Examples:
+        * eval musl sinf 1.234 # print the results of musl sinf(1.234f32)
+        * eval mpfr pow 1.234 2.432 # print the results of mpfr pow(1.234, 2.432)
+";
+
+fn main() {
+    let args = env::args().collect::<Vec<_>>();
+    let str_args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
+
+    match &str_args.as_slice()[1..] {
+        ["eval", basis, op, inputs @ ..] => do_eval(basis, op, inputs),
+        _ => {
+            println!("{USAGE}\nunrecognized input `{str_args:?}`");
+            std::process::exit(1);
+        }
+    }
+}
+
+macro_rules! handle_call {
+    (
+        fn_name: $fn_name:ident,
+        CFn: $CFn:ty,
+        RustFn: $RustFn:ty,
+        RustArgs: $RustArgs:ty,
+        attrs: [$($attr:meta),*],
+        extra: ($basis:ident, $op:ident, $inputs:ident),
+        fn_extra: $musl_fn:expr,
+    ) => {
+        $(#[$attr])*
+        if $op == stringify!($fn_name) {
+            type Op = libm_test::op::$fn_name::Routine;
+
+            let input = <$RustArgs>::parse($inputs);
+            let libm_fn: <Op as MathOp>::RustFn = libm::$fn_name;
+
+            let output = match $basis {
+                "libm" => input.call_intercept_panics(libm_fn),
+                #[cfg(feature = "build-musl")]
+                "musl" => {
+                    let musl_fn: <Op as MathOp>::CFn =
+                        $musl_fn.unwrap_or_else(|| panic!("no musl function for {}", $op));
+                    input.call(musl_fn)
+                }
+                #[cfg(feature = "build-mpfr")]
+                "mpfr" => {
+                    let mut mp = <Op as MpOp>::new_mp();
+                    Op::run(&mut mp, input)
+                }
+                _ => panic!("unrecognized or disabled basis '{}'", $basis),
+            };
+            println!("{output:?} {:x}", Hexf(output));
+            return;
+        }
+    };
+}
+
+/// Evaluate the specified operation with a given basis.
+fn do_eval(basis: &str, op: &str, inputs: &[&str]) {
+    libm_macros::for_each_function! {
+        callback: handle_call,
+        emit_types: [CFn, RustFn, RustArgs],
+        extra: (basis, op, inputs),
+        fn_extra: match MACRO_FN_NAME {
+            // Not provided by musl
+            fmaximum
+            | fmaximum_num
+            | fmaximum_numf
+            | fmaximumf
+            | fminimum
+            | fminimum_num
+            | fminimum_numf
+            | fminimumf
+            | roundeven
+            | roundevenf
+            | ALL_F16
+            | ALL_F128 => None,
+            _ => Some(musl_math_sys::MACRO_FN_NAME)
+        }
+    }
+
+    panic!("no operation matching {op}");
+}
+
+/// Parse a tuple from a space-delimited string.
+trait ParseTuple {
+    fn parse(input: &[&str]) -> Self;
+}
+
+macro_rules! impl_parse_tuple {
+    ($ty:ty) => {
+        impl ParseTuple for ($ty,) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 1, "expected a single argument, got {input:?}");
+                (parse(input, 0),)
+            }
+        }
+
+        impl ParseTuple for ($ty, $ty) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
+                (parse(input, 0), parse(input, 1))
+            }
+        }
+
+        impl ParseTuple for ($ty, i32) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
+                (parse(input, 0), parse(input, 1))
+            }
+        }
+
+        impl ParseTuple for (i32, $ty) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
+                (parse(input, 0), parse(input, 1))
+            }
+        }
+
+        impl ParseTuple for ($ty, $ty, $ty) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 3, "expected three arguments, got {input:?}");
+                (parse(input, 0), parse(input, 1), parse(input, 2))
+            }
+        }
+    };
+}
+
+#[allow(unused_macros)]
+#[cfg(feature = "build-mpfr")]
+macro_rules! impl_parse_tuple_via_rug {
+    ($ty:ty) => {
+        impl ParseTuple for ($ty,) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 1, "expected a single argument, got {input:?}");
+                (parse_rug(input, 0),)
+            }
+        }
+
+        impl ParseTuple for ($ty, $ty) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
+                (parse_rug(input, 0), parse_rug(input, 1))
+            }
+        }
+
+        impl ParseTuple for ($ty, i32) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
+                (parse_rug(input, 0), parse(input, 1))
+            }
+        }
+
+        impl ParseTuple for (i32, $ty) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
+                (parse(input, 0), parse_rug(input, 1))
+            }
+        }
+
+        impl ParseTuple for ($ty, $ty, $ty) {
+            fn parse(input: &[&str]) -> Self {
+                assert_eq!(input.len(), 3, "expected three arguments, got {input:?}");
+                (
+                    parse_rug(input, 0),
+                    parse_rug(input, 1),
+                    parse_rug(input, 2),
+                )
+            }
+        }
+    };
+}
+
+// Fallback for when Rug is not built.
+#[allow(unused_macros)]
+#[cfg(not(feature = "build-mpfr"))]
+macro_rules! impl_parse_tuple_via_rug {
+    ($ty:ty) => {
+        impl ParseTuple for ($ty,) {
+            fn parse(_input: &[&str]) -> Self {
+                panic!("parsing this type requires the `build-mpfr` feature")
+            }
+        }
+
+        impl ParseTuple for ($ty, $ty) {
+            fn parse(_input: &[&str]) -> Self {
+                panic!("parsing this type requires the `build-mpfr` feature")
+            }
+        }
+
+        impl ParseTuple for ($ty, i32) {
+            fn parse(_input: &[&str]) -> Self {
+                panic!("parsing this type requires the `build-mpfr` feature")
+            }
+        }
+
+        impl ParseTuple for (i32, $ty) {
+            fn parse(_input: &[&str]) -> Self {
+                panic!("parsing this type requires the `build-mpfr` feature")
+            }
+        }
+
+        impl ParseTuple for ($ty, $ty, $ty) {
+            fn parse(_input: &[&str]) -> Self {
+                panic!("parsing this type requires the `build-mpfr` feature")
+            }
+        }
+    };
+}
+
+impl_parse_tuple!(f32);
+impl_parse_tuple!(f64);
+
+#[cfg(f16_enabled)]
+impl_parse_tuple_via_rug!(f16);
+#[cfg(f128_enabled)]
+impl_parse_tuple_via_rug!(f128);
+
+/// Try to parse the number, printing a nice message on failure.
+fn parse<T: FromStr + FromStrRadix>(input: &[&str], idx: usize) -> T {
+    let s = input[idx];
+
+    let msg = || format!("invalid {} input '{s}'", type_name::<T>());
+
+    if s.starts_with("0x") || s.starts_with("-0x") {
+        return T::from_str_radix(s, 16).unwrap_or_else(|_| panic!("{}", msg()));
+    }
+
+    if s.starts_with("0b") {
+        return T::from_str_radix(s, 2).unwrap_or_else(|_| panic!("{}", msg()));
+    }
+
+    s.parse().unwrap_or_else(|_| panic!("{}", msg()))
+}
+
+/// Try to parse the float type going via `rug`, for `f16` and `f128` which don't yet implement
+/// `FromStr`.
+#[cfg(feature = "build-mpfr")]
+fn parse_rug<F>(input: &[&str], idx: usize) -> F
+where
+    F: libm_test::Float + FromStrRadix,
+    rug::Float: az::Cast<F>,
+{
+    let s = input[idx];
+
+    let msg = || format!("invalid {} input '{s}'", type_name::<F>());
+
+    if s.starts_with("0x") {
+        return F::from_str_radix(s, 16).unwrap_or_else(|_| panic!("{}", msg()));
+    }
+
+    if s.starts_with("0b") {
+        return F::from_str_radix(s, 2).unwrap_or_else(|_| panic!("{}", msg()));
+    }
+
+    let x = rug::Float::parse(s).unwrap_or_else(|_| panic!("{}", msg()));
+    let x = rug::Float::with_val(F::BITS, x);
+    x.az()
+}
+
+trait FromStrRadix: Sized {
+    fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError>;
+}
+
+impl FromStrRadix for i32 {
+    fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
+        let s = strip_radix_prefix(s, radix);
+        i32::from_str_radix(s, radix)
+    }
+}
+
+#[cfg(f16_enabled)]
+impl FromStrRadix for f16 {
+    fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
+        if radix == 16 && s.contains("p") {
+            return Ok(libm::support::hf16(s));
+        }
+
+        let s = strip_radix_prefix(s, radix);
+        u16::from_str_radix(s, radix).map(Self::from_bits)
+    }
+}
+
+impl FromStrRadix for f32 {
+    fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
+        if radix == 16 && s.contains("p") {
+            // Parse as hex float
+            return Ok(hf32(s));
+        }
+
+        let s = strip_radix_prefix(s, radix);
+        u32::from_str_radix(s, radix).map(Self::from_bits)
+    }
+}
+
+impl FromStrRadix for f64 {
+    fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
+        if s.contains("p") {
+            return Ok(hf64(s));
+        }
+
+        let s = strip_radix_prefix(s, radix);
+        u64::from_str_radix(s, radix).map(Self::from_bits)
+    }
+}
+
+#[cfg(f128_enabled)]
+impl FromStrRadix for f128 {
+    fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
+        if radix == 16 && s.contains("p") {
+            return Ok(libm::support::hf128(s));
+        }
+        let s = strip_radix_prefix(s, radix);
+        u128::from_str_radix(s, radix).map(Self::from_bits)
+    }
+}
+
+fn strip_radix_prefix(s: &str, radix: u32) -> &str {
+    if radix == 16 {
+        s.strip_prefix("0x").unwrap()
+    } else if radix == 2 {
+        s.strip_prefix("0b").unwrap()
+    } else {
+        s
+    }
+}