about summary refs log tree commit diff
path: root/library/compiler-builtins/crates
diff options
context:
space:
mode:
Diffstat (limited to 'library/compiler-builtins/crates')
-rw-r--r--library/compiler-builtins/crates/libm-macros/Cargo.toml22
-rw-r--r--library/compiler-builtins/crates/libm-macros/src/enums.rs171
-rw-r--r--library/compiler-builtins/crates/libm-macros/src/lib.rs504
-rw-r--r--library/compiler-builtins/crates/libm-macros/src/parse.rs296
-rw-r--r--library/compiler-builtins/crates/libm-macros/src/shared.rs590
-rw-r--r--library/compiler-builtins/crates/libm-macros/tests/basic.rs177
-rw-r--r--library/compiler-builtins/crates/libm-macros/tests/enum.rs38
-rw-r--r--library/compiler-builtins/crates/musl-math-sys/Cargo.toml14
-rw-r--r--library/compiler-builtins/crates/musl-math-sys/build.rs350
-rw-r--r--library/compiler-builtins/crates/musl-math-sys/c_patches/alias.c40
-rw-r--r--library/compiler-builtins/crates/musl-math-sys/c_patches/features.h39
m---------library/compiler-builtins/crates/musl-math-sys/musl0
-rw-r--r--library/compiler-builtins/crates/musl-math-sys/src/lib.rs287
-rw-r--r--library/compiler-builtins/crates/panic-handler/Cargo.toml12
-rw-r--r--library/compiler-builtins/crates/panic-handler/src/lib.rs11
-rw-r--r--library/compiler-builtins/crates/util/Cargo.toml19
-rw-r--r--library/compiler-builtins/crates/util/build.rs10
-rw-r--r--library/compiler-builtins/crates/util/src/main.rs350
18 files changed, 2930 insertions, 0 deletions
diff --git a/library/compiler-builtins/crates/libm-macros/Cargo.toml b/library/compiler-builtins/crates/libm-macros/Cargo.toml
new file mode 100644
index 00000000000..3929854f08e
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "libm-macros"
+version = "0.1.0"
+edition = "2024"
+publish = false
+license = "MIT OR Apache-2.0"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+heck = "0.5.0"
+proc-macro2 = "1.0.94"
+quote = "1.0.40"
+syn = { version = "2.0.100", features = ["full", "extra-traits", "visit-mut"] }
+
+[lints.rust]
+# Values used during testing
+unexpected_cfgs = { level = "warn", check-cfg = [
+  'cfg(f16_enabled)',
+  'cfg(f128_enabled)',
+] }
diff --git a/library/compiler-builtins/crates/libm-macros/src/enums.rs b/library/compiler-builtins/crates/libm-macros/src/enums.rs
new file mode 100644
index 00000000000..b4646f984d4
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/src/enums.rs
@@ -0,0 +1,171 @@
+use heck::ToUpperCamelCase;
+use proc_macro2 as pm2;
+use proc_macro2::{Ident, Span};
+use quote::quote;
+use syn::spanned::Spanned;
+use syn::{Fields, ItemEnum, Variant};
+
+use crate::{ALL_OPERATIONS, base_name};
+
+/// Implement `#[function_enum]`, see documentation in `lib.rs`.
+pub fn function_enum(
+    mut item: ItemEnum,
+    attributes: pm2::TokenStream,
+) -> syn::Result<pm2::TokenStream> {
+    expect_empty_enum(&item)?;
+    let attr_span = attributes.span();
+    let mut attr = attributes.into_iter();
+
+    // Attribute should be the identifier of the `BaseName` enum.
+    let Some(tt) = attr.next() else {
+        return Err(syn::Error::new(attr_span, "expected one attribute"));
+    };
+
+    let pm2::TokenTree::Ident(base_enum) = tt else {
+        return Err(syn::Error::new(tt.span(), "expected an identifier"));
+    };
+
+    if let Some(tt) = attr.next() {
+        return Err(syn::Error::new(
+            tt.span(),
+            "unexpected token after identifier",
+        ));
+    }
+
+    let enum_name = &item.ident;
+    let mut as_str_arms = Vec::new();
+    let mut from_str_arms = Vec::new();
+    let mut base_arms = Vec::new();
+
+    for func in ALL_OPERATIONS.iter() {
+        let fn_name = func.name;
+        let ident = Ident::new(&fn_name.to_upper_camel_case(), Span::call_site());
+        let bname_ident = Ident::new(&base_name(fn_name).to_upper_camel_case(), Span::call_site());
+
+        // Match arm for `fn as_str(self)` matcher
+        as_str_arms.push(quote! { Self::#ident => #fn_name });
+        from_str_arms.push(quote! { #fn_name => Self::#ident });
+
+        // Match arm for `fn base_name(self)` matcher
+        base_arms.push(quote! { Self::#ident => #base_enum::#bname_ident });
+
+        let variant = Variant {
+            attrs: Vec::new(),
+            ident,
+            fields: Fields::Unit,
+            discriminant: None,
+        };
+
+        item.variants.push(variant);
+    }
+
+    let variants = item.variants.iter();
+
+    let res = quote! {
+        // Instantiate the enum
+        #item
+
+        impl #enum_name {
+            /// All variants of this enum.
+            pub const ALL: &[Self] = &[
+                #( Self::#variants, )*
+            ];
+
+            /// The stringified version of this function name.
+            pub const fn as_str(self) -> &'static str {
+                match self {
+                    #( #as_str_arms , )*
+                }
+            }
+
+            /// If `s` is the name of a function, return it.
+            pub fn from_str(s: &str) -> Option<Self> {
+                let ret = match s {
+                    #( #from_str_arms , )*
+                    _ => return None,
+                };
+                Some(ret)
+            }
+
+            /// The base name enum for this function.
+            pub const fn base_name(self) -> #base_enum {
+                match self {
+                    #( #base_arms, )*
+                }
+            }
+
+            /// Return information about this operation.
+            pub fn math_op(self) -> &'static crate::op::MathOpInfo {
+                crate::op::ALL_OPERATIONS.iter().find(|op| op.name == self.as_str()).unwrap()
+            }
+        }
+    };
+
+    Ok(res)
+}
+
+/// Implement `#[base_name_enum]`, see documentation in `lib.rs`.
+pub fn base_name_enum(
+    mut item: ItemEnum,
+    attributes: pm2::TokenStream,
+) -> syn::Result<pm2::TokenStream> {
+    expect_empty_enum(&item)?;
+    if !attributes.is_empty() {
+        let sp = attributes.span();
+        return Err(syn::Error::new(sp.span(), "no attributes expected"));
+    }
+
+    let mut base_names: Vec<_> = ALL_OPERATIONS
+        .iter()
+        .map(|func| base_name(func.name))
+        .collect();
+    base_names.sort_unstable();
+    base_names.dedup();
+
+    let item_name = &item.ident;
+    let mut as_str_arms = Vec::new();
+
+    for base_name in base_names {
+        let ident = Ident::new(&base_name.to_upper_camel_case(), Span::call_site());
+
+        // Match arm for `fn as_str(self)` matcher
+        as_str_arms.push(quote! { Self::#ident => #base_name });
+
+        let variant = Variant {
+            attrs: Vec::new(),
+            ident,
+            fields: Fields::Unit,
+            discriminant: None,
+        };
+
+        item.variants.push(variant);
+    }
+
+    let res = quote! {
+        // Instantiate the enum
+        #item
+
+        impl #item_name {
+            /// The stringified version of this base name.
+            pub const fn as_str(self) -> &'static str {
+                match self {
+                    #( #as_str_arms ),*
+                }
+            }
+        }
+    };
+
+    Ok(res)
+}
+
+/// Verify that an enum is empty, otherwise return an error
+fn expect_empty_enum(item: &ItemEnum) -> syn::Result<()> {
+    if !item.variants.is_empty() {
+        Err(syn::Error::new(
+            item.variants.span(),
+            "expected an empty enum",
+        ))
+    } else {
+        Ok(())
+    }
+}
diff --git a/library/compiler-builtins/crates/libm-macros/src/lib.rs b/library/compiler-builtins/crates/libm-macros/src/lib.rs
new file mode 100644
index 00000000000..482da974ca8
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/src/lib.rs
@@ -0,0 +1,504 @@
+#![feature(let_chains)]
+
+mod enums;
+mod parse;
+mod shared;
+
+use parse::{Invocation, StructuredInput};
+use proc_macro as pm;
+use proc_macro2::{self as pm2, Span};
+use quote::{ToTokens, quote};
+pub(crate) use shared::{ALL_OPERATIONS, FloatTy, MathOpInfo, Ty};
+use syn::spanned::Spanned;
+use syn::visit_mut::VisitMut;
+use syn::{Ident, ItemEnum};
+
+const KNOWN_TYPES: &[&str] = &[
+    "FTy", "CFn", "CArgs", "CRet", "RustFn", "RustArgs", "RustRet", "public",
+];
+
+/// Populate an enum with a variant representing function. Names are in upper camel case.
+///
+/// Applied to an empty enum. Expects one attribute `#[function_enum(BaseName)]` that provides
+/// the name of the `BaseName` enum.
+#[proc_macro_attribute]
+pub fn function_enum(attributes: pm::TokenStream, tokens: pm::TokenStream) -> pm::TokenStream {
+    let item = syn::parse_macro_input!(tokens as ItemEnum);
+    let res = enums::function_enum(item, attributes.into());
+
+    match res {
+        Ok(ts) => ts,
+        Err(e) => e.into_compile_error(),
+    }
+    .into()
+}
+
+/// Create an enum representing all possible base names, with names in upper camel case.
+///
+/// Applied to an empty enum.
+#[proc_macro_attribute]
+pub fn base_name_enum(attributes: pm::TokenStream, tokens: pm::TokenStream) -> pm::TokenStream {
+    let item = syn::parse_macro_input!(tokens as ItemEnum);
+    let res = enums::base_name_enum(item, attributes.into());
+
+    match res {
+        Ok(ts) => ts,
+        Err(e) => e.into_compile_error(),
+    }
+    .into()
+}
+
+/// Do something for each function present in this crate.
+///
+/// Takes a callback macro and invokes it multiple times, once for each function that
+/// this crate exports. This makes it easy to create generic tests, benchmarks, or other checks
+/// and apply it to each symbol.
+///
+/// Additionally, the `extra` and `fn_extra` patterns can make use of magic identifiers:
+///
+/// - `MACRO_FN_NAME`: gets replaced with the name of the function on that invocation.
+/// - `MACRO_FN_NAME_NORMALIZED`: similar to the above, but removes sufixes so e.g. `sinf` becomes
+///   `sin`, `cosf128` becomes `cos`, etc.
+///
+/// Invoke as:
+///
+/// ```
+/// // Macro that is invoked once per function
+/// macro_rules! callback_macro {
+///     (
+///         // Name of that function
+///         fn_name: $fn_name:ident,
+///         // The basic float type for this function (e.g. `f32`, `f64`)
+///         FTy: $FTy:ty,
+///         // Function signature of the C version (e.g. `fn(f32, &mut f32) -> f32`)
+///         CFn: $CFn:ty,
+///         // A tuple representing the C version's arguments (e.g. `(f32, &mut f32)`)
+///         CArgs: $CArgs:ty,
+///         // The C version's return type (e.g. `f32`)
+///         CRet: $CRet:ty,
+///         // Function signature of the Rust version (e.g. `fn(f32) -> (f32, f32)`)
+///         RustFn: $RustFn:ty,
+///         // A tuple representing the Rust version's arguments (e.g. `(f32,)`)
+///         RustArgs: $RustArgs:ty,
+///         // The Rust version's return type (e.g. `(f32, f32)`)
+///         RustRet: $RustRet:ty,
+///         // True if this is part of `libm`'s public API
+///         public: $public:expr,
+///         // Attributes for the current function, if any
+///         attrs: [$($attr:meta),*],
+///         // Extra tokens passed directly (if any)
+///         extra: [$extra:ident],
+///         // Extra function-tokens passed directly (if any)
+///         fn_extra: $fn_extra:expr,
+///     ) => { };
+/// }
+///
+/// // All fields except for `callback` are optional.
+/// libm_macros::for_each_function! {
+///     // The macro to invoke as a callback
+///     callback: callback_macro,
+///     // Which types to include either as a list (`[CFn, RustFn, RustArgs]`) or "all"
+///     emit_types: all,
+///     // Functions to skip, i.e. `callback` shouldn't be called at all for these.
+///     skip: [sin, cos],
+///     // Attributes passed as `attrs` for specific functions. For example, here the invocation
+///     // with `sinf` and that with `cosf` will both get `meta1` and `meta2`, but no others will.
+///     //
+///     // Note that `f16_enabled` and `f128_enabled` will always get emitted regardless of whether
+///     // or not this is specified.
+///     attributes: [
+///         #[meta1]
+///         #[meta2]
+///         [sinf, cosf],
+///     ],
+///     // Any tokens that should be passed directly to all invocations of the callback. This can
+///     // be used to pass local variables or other things the macro needs access to.
+///     extra: [foo],
+///     // Similar to `extra`, but allow providing a pattern for only specific functions. Uses
+///     // a simplified match-like syntax.
+///     fn_extra: match MACRO_FN_NAME {
+///         hypot | hypotf => |x| x.hypot(),
+///         // `ALL_*` magic matchers also work to extract specific types
+///         ALL_F64 => |x| x,
+///         // The default pattern gets applied to everything that did not match
+///         _ => |x| x,
+///     },
+/// }
+/// ```
+#[proc_macro]
+pub fn for_each_function(tokens: pm::TokenStream) -> pm::TokenStream {
+    let input = syn::parse_macro_input!(tokens as Invocation);
+
+    let res = StructuredInput::from_fields(input)
+        .and_then(|mut s_in| validate(&mut s_in).map(|fn_list| (s_in, fn_list)))
+        .and_then(|(s_in, fn_list)| expand(s_in, &fn_list));
+
+    match res {
+        Ok(ts) => ts.into(),
+        Err(e) => e.into_compile_error().into(),
+    }
+}
+
+/// Check for any input that is structurally correct but has other problems.
+///
+/// Returns the list of function names that we should expand for.
+fn validate(input: &mut StructuredInput) -> syn::Result<Vec<&'static MathOpInfo>> {
+    // Replace magic mappers with a list of relevant functions.
+    if let Some(map) = &mut input.fn_extra {
+        for (name, ty) in [
+            ("ALL_F16", FloatTy::F16),
+            ("ALL_F32", FloatTy::F32),
+            ("ALL_F64", FloatTy::F64),
+            ("ALL_F128", FloatTy::F128),
+        ] {
+            let Some(k) = map.keys().find(|key| *key == name) else {
+                continue;
+            };
+
+            let key = k.clone();
+            let val = map.remove(&key).unwrap();
+
+            for op in ALL_OPERATIONS.iter().filter(|op| op.float_ty == ty) {
+                map.insert(Ident::new(op.name, key.span()), val.clone());
+            }
+        }
+    }
+
+    // Collect lists of all functions that are provied as macro inputs in various fields (only,
+    // skip, attributes).
+    let attr_mentions = input
+        .attributes
+        .iter()
+        .flat_map(|map_list| map_list.iter())
+        .flat_map(|attr_map| attr_map.names.iter());
+    let only_mentions = input.only.iter().flat_map(|only_list| only_list.iter());
+    let fn_extra_mentions = input
+        .fn_extra
+        .iter()
+        .flat_map(|v| v.keys())
+        .filter(|name| *name != "_");
+    let all_mentioned_fns = input
+        .skip
+        .iter()
+        .chain(only_mentions)
+        .chain(attr_mentions)
+        .chain(fn_extra_mentions);
+
+    // Make sure that every function mentioned is a real function
+    for mentioned in all_mentioned_fns {
+        if !ALL_OPERATIONS.iter().any(|func| mentioned == func.name) {
+            let e = syn::Error::new(
+                mentioned.span(),
+                format!("unrecognized function name `{mentioned}`"),
+            );
+            return Err(e);
+        }
+    }
+
+    if !input.skip.is_empty() && input.only.is_some() {
+        let e = syn::Error::new(
+            input.only_span.unwrap(),
+            "only one of `skip` or `only` may be specified",
+        );
+        return Err(e);
+    }
+
+    // Construct a list of what we intend to expand
+    let mut fn_list = Vec::new();
+    for func in ALL_OPERATIONS.iter() {
+        let fn_name = func.name;
+        // If we have an `only` list and it does _not_ contain this function name, skip it
+        if input
+            .only
+            .as_ref()
+            .is_some_and(|only| !only.iter().any(|o| o == fn_name))
+        {
+            continue;
+        }
+
+        // If there is a `skip` list that contains this function name, skip it
+        if input.skip.iter().any(|s| s == fn_name) {
+            continue;
+        }
+
+        // Omit f16 and f128 functions if requested
+        if input.skip_f16_f128 && (func.float_ty == FloatTy::F16 || func.float_ty == FloatTy::F128)
+        {
+            continue;
+        }
+
+        // Run everything else
+        fn_list.push(func);
+    }
+
+    // Types that the user would like us to provide in the macro
+    let mut add_all_types = false;
+    for ty in &input.emit_types {
+        let ty_name = ty.to_string();
+        if ty_name == "all" {
+            add_all_types = true;
+            continue;
+        }
+
+        // Check that all requested types are valid
+        if !KNOWN_TYPES.contains(&ty_name.as_str()) {
+            let e = syn::Error::new(
+                ty_name.span(),
+                format!("unrecognized type identifier `{ty_name}`"),
+            );
+            return Err(e);
+        }
+    }
+
+    if add_all_types {
+        // Ensure that if `all` was specified that nothing else was
+        if input.emit_types.len() > 1 {
+            let e = syn::Error::new(
+                input.emit_types_span.unwrap(),
+                "if `all` is specified, no other type identifiers may be given",
+            );
+            return Err(e);
+        }
+
+        // ...and then add all types
+        input.emit_types.clear();
+        for ty in KNOWN_TYPES {
+            let ident = Ident::new(ty, Span::call_site());
+            input.emit_types.push(ident);
+        }
+    }
+
+    if let Some(map) = &input.fn_extra
+        && !map.keys().any(|key| key == "_")
+    {
+        // No default provided; make sure every expected function is covered
+        let mut fns_not_covered = Vec::new();
+        for func in &fn_list {
+            if !map.keys().any(|key| key == func.name) {
+                // `name` was not mentioned in the `match` statement
+                fns_not_covered.push(func);
+            }
+        }
+
+        if !fns_not_covered.is_empty() {
+            let e = syn::Error::new(
+                input.fn_extra_span.unwrap(),
+                format!(
+                    "`fn_extra`: no default `_` pattern specified and the following \
+                     patterns are not covered: {fns_not_covered:#?}"
+                ),
+            );
+            return Err(e);
+        }
+    };
+
+    Ok(fn_list)
+}
+
+/// Expand our structured macro input into invocations of the callback macro.
+fn expand(input: StructuredInput, fn_list: &[&MathOpInfo]) -> syn::Result<pm2::TokenStream> {
+    let mut out = pm2::TokenStream::new();
+    let default_ident = Ident::new("_", Span::call_site());
+    let callback = input.callback;
+
+    for func in fn_list {
+        let fn_name = Ident::new(func.name, Span::call_site());
+
+        // Prepare attributes in an `attrs: ...` field
+        let mut meta_fields = Vec::new();
+        if let Some(attrs) = &input.attributes {
+            let meta_iter = attrs
+                .iter()
+                .filter(|map| map.names.contains(&fn_name))
+                .flat_map(|map| &map.meta)
+                .map(|v| v.into_token_stream());
+
+            meta_fields.extend(meta_iter);
+        }
+
+        // Always emit f16 and f128 meta so this doesn't need to be repeated everywhere
+        if func.rust_sig.args.contains(&Ty::F16) || func.rust_sig.returns.contains(&Ty::F16) {
+            let ts = quote! { cfg(f16_enabled) };
+            meta_fields.push(ts);
+        }
+        if func.rust_sig.args.contains(&Ty::F128) || func.rust_sig.returns.contains(&Ty::F128) {
+            let ts = quote! { cfg(f128_enabled) };
+            meta_fields.push(ts);
+        }
+
+        let meta_field = quote! { attrs: [ #( #meta_fields ),* ], };
+
+        // Prepare extra in an `extra: ...` field, running the replacer
+        let extra_field = match input.extra.clone() {
+            Some(mut extra) => {
+                let mut v = MacroReplace::new(func.name);
+                v.visit_expr_mut(&mut extra);
+                v.finish()?;
+
+                quote! { extra: #extra, }
+            }
+            None => pm2::TokenStream::new(),
+        };
+
+        // Prepare function-specific extra in a `fn_extra: ...` field, running the replacer
+        let fn_extra_field = match input.fn_extra {
+            Some(ref map) => {
+                let mut fn_extra = map
+                    .get(&fn_name)
+                    .or_else(|| map.get(&default_ident))
+                    .unwrap()
+                    .clone();
+
+                let mut v = MacroReplace::new(func.name);
+                v.visit_expr_mut(&mut fn_extra);
+                v.finish()?;
+
+                quote! { fn_extra: #fn_extra, }
+            }
+            None => pm2::TokenStream::new(),
+        };
+
+        let base_fty = func.float_ty;
+        let c_args = &func.c_sig.args;
+        let c_ret = &func.c_sig.returns;
+        let rust_args = &func.rust_sig.args;
+        let rust_ret = &func.rust_sig.returns;
+        let public = func.public;
+
+        let mut ty_fields = Vec::new();
+        for ty in &input.emit_types {
+            let field = match ty.to_string().as_str() {
+                "FTy" => quote! { FTy: #base_fty, },
+                "CFn" => quote! { CFn: fn( #(#c_args),* ,) -> ( #(#c_ret),* ), },
+                "CArgs" => quote! { CArgs: ( #(#c_args),* ,), },
+                "CRet" => quote! { CRet: ( #(#c_ret),* ), },
+                "RustFn" => quote! { RustFn: fn( #(#rust_args),* ,) -> ( #(#rust_ret),* ), },
+                "RustArgs" => quote! { RustArgs: ( #(#rust_args),* ,), },
+                "RustRet" => quote! { RustRet: ( #(#rust_ret),* ), },
+                "public" => quote! { public: #public, },
+                _ => unreachable!("checked in validation"),
+            };
+            ty_fields.push(field);
+        }
+
+        let new = quote! {
+            #callback! {
+                fn_name: #fn_name,
+                #( #ty_fields )*
+                #meta_field
+                #extra_field
+                #fn_extra_field
+            }
+        };
+
+        out.extend(new);
+    }
+
+    Ok(out)
+}
+
+/// Visitor to replace "magic" identifiers that we allow: `MACRO_FN_NAME` and
+/// `MACRO_FN_NAME_NORMALIZED`.
+struct MacroReplace {
+    fn_name: &'static str,
+    /// Remove the trailing `f` or `f128` to make
+    norm_name: String,
+    error: Option<syn::Error>,
+}
+
+impl MacroReplace {
+    fn new(name: &'static str) -> Self {
+        let norm_name = base_name(name);
+        Self {
+            fn_name: name,
+            norm_name: norm_name.to_owned(),
+            error: None,
+        }
+    }
+
+    fn finish(self) -> syn::Result<()> {
+        match self.error {
+            Some(e) => Err(e),
+            None => Ok(()),
+        }
+    }
+
+    fn visit_ident_inner(&mut self, i: &mut Ident) {
+        let s = i.to_string();
+        if !s.starts_with("MACRO") || self.error.is_some() {
+            return;
+        }
+
+        match s.as_str() {
+            "MACRO_FN_NAME" => *i = Ident::new(self.fn_name, i.span()),
+            "MACRO_FN_NAME_NORMALIZED" => *i = Ident::new(&self.norm_name, i.span()),
+            _ => {
+                self.error = Some(syn::Error::new(
+                    i.span(),
+                    format!("unrecognized meta expression `{s}`"),
+                ));
+            }
+        }
+    }
+}
+
+impl VisitMut for MacroReplace {
+    fn visit_ident_mut(&mut self, i: &mut Ident) {
+        self.visit_ident_inner(i);
+        syn::visit_mut::visit_ident_mut(self, i);
+    }
+}
+
+/// Return the unsuffixed version of a function name; e.g. `abs` and `absf` both return `abs`,
+/// `lgamma_r` and `lgammaf_r` both return `lgamma_r`.
+fn base_name(name: &str) -> &str {
+    let known_mappings = &[
+        ("erff", "erf"),
+        ("erf", "erf"),
+        ("lgammaf_r", "lgamma_r"),
+        ("modff", "modf"),
+        ("modf", "modf"),
+    ];
+
+    match known_mappings.iter().find(|known| known.0 == name) {
+        Some(found) => found.1,
+        None => name
+            .strip_suffix("f")
+            .or_else(|| name.strip_suffix("f16"))
+            .or_else(|| name.strip_suffix("f128"))
+            .unwrap_or(name),
+    }
+}
+
+impl ToTokens for Ty {
+    fn to_tokens(&self, tokens: &mut pm2::TokenStream) {
+        let ts = match self {
+            Ty::F16 => quote! { f16 },
+            Ty::F32 => quote! { f32 },
+            Ty::F64 => quote! { f64 },
+            Ty::F128 => quote! { f128 },
+            Ty::I32 => quote! { i32 },
+            Ty::CInt => quote! { ::core::ffi::c_int },
+            Ty::MutF16 => quote! { &'a mut f16 },
+            Ty::MutF32 => quote! { &'a mut f32 },
+            Ty::MutF64 => quote! { &'a mut f64 },
+            Ty::MutF128 => quote! { &'a mut f128 },
+            Ty::MutI32 => quote! { &'a mut i32 },
+            Ty::MutCInt => quote! { &'a mut core::ffi::c_int },
+        };
+
+        tokens.extend(ts);
+    }
+}
+impl ToTokens for FloatTy {
+    fn to_tokens(&self, tokens: &mut pm2::TokenStream) {
+        let ts = match self {
+            FloatTy::F16 => quote! { f16 },
+            FloatTy::F32 => quote! { f32 },
+            FloatTy::F64 => quote! { f64 },
+            FloatTy::F128 => quote! { f128 },
+        };
+
+        tokens.extend(ts);
+    }
+}
diff --git a/library/compiler-builtins/crates/libm-macros/src/parse.rs b/library/compiler-builtins/crates/libm-macros/src/parse.rs
new file mode 100644
index 00000000000..4876f3ef726
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/src/parse.rs
@@ -0,0 +1,296 @@
+use std::collections::BTreeMap;
+
+use proc_macro2::Span;
+use quote::ToTokens;
+use syn::parse::{Parse, ParseStream, Parser};
+use syn::punctuated::Punctuated;
+use syn::spanned::Spanned;
+use syn::token::{self, Comma};
+use syn::{Arm, Attribute, Expr, ExprMatch, Ident, LitBool, Meta, Token, bracketed};
+
+/// The input to our macro; just a list of `field: value` items.
+#[derive(Debug)]
+pub struct Invocation {
+    fields: Punctuated<Mapping, Comma>,
+}
+
+impl Parse for Invocation {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        Ok(Self {
+            fields: input.parse_terminated(Mapping::parse, Token![,])?,
+        })
+    }
+}
+
+/// A `key: expression` mapping with nothing else. Basically a simplified `syn::Field`.
+#[derive(Debug)]
+struct Mapping {
+    name: Ident,
+    _sep: Token![:],
+    expr: Expr,
+}
+
+impl Parse for Mapping {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        Ok(Self {
+            name: input.parse()?,
+            _sep: input.parse()?,
+            expr: input.parse()?,
+        })
+    }
+}
+
+/// The input provided to our proc macro, after parsing into the form we expect.
+#[derive(Debug)]
+pub struct StructuredInput {
+    /// Macro to invoke once per function
+    pub callback: Ident,
+    /// Whether or not to provide `CFn` `CArgs` `RustFn` etc. This is really only needed
+    /// once for crate to set up the main trait.
+    pub emit_types: Vec<Ident>,
+    /// Skip these functions
+    pub skip: Vec<Ident>,
+    /// If true, omit f16 and f128 functions that aren't present in other libraries.
+    pub skip_f16_f128: bool,
+    /// Invoke only for these functions
+    pub only: Option<Vec<Ident>>,
+    /// Attributes that get applied to specific functions
+    pub attributes: Option<Vec<AttributeMap>>,
+    /// Extra expressions to pass to all invocations of the macro
+    pub extra: Option<Expr>,
+    /// Per-function extra expressions to pass to the macro
+    pub fn_extra: Option<BTreeMap<Ident, Expr>>,
+    // For diagnostics
+    pub emit_types_span: Option<Span>,
+    pub only_span: Option<Span>,
+    pub fn_extra_span: Option<Span>,
+}
+
+impl StructuredInput {
+    pub fn from_fields(input: Invocation) -> syn::Result<Self> {
+        let mut map: Vec<_> = input.fields.into_iter().collect();
+        let cb_expr = expect_field(&mut map, "callback")?;
+        let emit_types_expr = expect_field(&mut map, "emit_types").ok();
+        let skip_expr = expect_field(&mut map, "skip").ok();
+        let skip_f16_f128 = expect_field(&mut map, "skip_f16_f128").ok();
+        let only_expr = expect_field(&mut map, "only").ok();
+        let attr_expr = expect_field(&mut map, "attributes").ok();
+        let extra = expect_field(&mut map, "extra").ok();
+        let fn_extra = expect_field(&mut map, "fn_extra").ok();
+
+        if !map.is_empty() {
+            Err(syn::Error::new(
+                map.first().unwrap().name.span(),
+                format!("unexpected fields {map:?}"),
+            ))?;
+        }
+
+        let emit_types_span = emit_types_expr.as_ref().map(|expr| expr.span());
+        let emit_types = match emit_types_expr {
+            Some(expr) => Parser::parse2(parse_ident_or_array, expr.into_token_stream())?,
+            None => Vec::new(),
+        };
+
+        let skip = match skip_expr {
+            Some(expr) => Parser::parse2(parse_ident_array, expr.into_token_stream())?,
+            None => Vec::new(),
+        };
+
+        let skip_f16_f128 = match skip_f16_f128 {
+            Some(expr) => expect_litbool(expr)?.value,
+            None => false,
+        };
+
+        let only_span = only_expr.as_ref().map(|expr| expr.span());
+        let only = match only_expr {
+            Some(expr) => Some(Parser::parse2(parse_ident_array, expr.into_token_stream())?),
+            None => None,
+        };
+
+        let attributes = match attr_expr {
+            Some(expr) => {
+                let mut attributes = Vec::new();
+                let attr_exprs = Parser::parse2(parse_expr_array, expr.into_token_stream())?;
+
+                for attr in attr_exprs {
+                    attributes.push(syn::parse2(attr.into_token_stream())?);
+                }
+                Some(attributes)
+            }
+            None => None,
+        };
+
+        let fn_extra_span = fn_extra.as_ref().map(|expr| expr.span());
+        let fn_extra = match fn_extra {
+            Some(expr) => Some(extract_fn_extra_field(expr)?),
+            None => None,
+        };
+
+        Ok(Self {
+            callback: expect_ident(cb_expr)?,
+            emit_types,
+            skip,
+            skip_f16_f128,
+            only,
+            only_span,
+            attributes,
+            extra,
+            fn_extra,
+            fn_extra_span,
+            emit_types_span,
+        })
+    }
+}
+
+fn extract_fn_extra_field(expr: Expr) -> syn::Result<BTreeMap<Ident, Expr>> {
+    let Expr::Match(mexpr) = expr else {
+        let e = syn::Error::new(expr.span(), "`fn_extra` expects a match expression");
+        return Err(e);
+    };
+
+    let ExprMatch {
+        attrs,
+        match_token: _,
+        expr,
+        brace_token: _,
+        arms,
+    } = mexpr;
+
+    expect_empty_attrs(&attrs)?;
+
+    let match_on = expect_ident(*expr)?;
+    if match_on != "MACRO_FN_NAME" {
+        let e = syn::Error::new(match_on.span(), "only allowed to match on `MACRO_FN_NAME`");
+        return Err(e);
+    }
+
+    let mut res = BTreeMap::new();
+
+    for arm in arms {
+        let Arm {
+            attrs,
+            pat,
+            guard,
+            fat_arrow_token: _,
+            body,
+            comma: _,
+        } = arm;
+
+        expect_empty_attrs(&attrs)?;
+
+        let keys = match pat {
+            syn::Pat::Wild(w) => vec![Ident::new("_", w.span())],
+            _ => Parser::parse2(parse_ident_pat, pat.into_token_stream())?,
+        };
+
+        if let Some(guard) = guard {
+            let e = syn::Error::new(guard.0.span(), "no guards allowed in this position");
+            return Err(e);
+        }
+
+        for key in keys {
+            let inserted = res.insert(key.clone(), *body.clone());
+            if inserted.is_some() {
+                let e = syn::Error::new(key.span(), format!("key `{key}` specified twice"));
+                return Err(e);
+            }
+        }
+    }
+
+    Ok(res)
+}
+
+fn expect_empty_attrs(attrs: &[Attribute]) -> syn::Result<()> {
+    if attrs.is_empty() {
+        return Ok(());
+    }
+
+    let e = syn::Error::new(
+        attrs.first().unwrap().span(),
+        "no attributes allowed in this position",
+    );
+    Err(e)
+}
+
+/// Extract a named field from a map, raising an error if it doesn't exist.
+fn expect_field(v: &mut Vec<Mapping>, name: &str) -> syn::Result<Expr> {
+    let pos = v.iter().position(|v| v.name == name).ok_or_else(|| {
+        syn::Error::new(
+            Span::call_site(),
+            format!("missing expected field `{name}`"),
+        )
+    })?;
+
+    Ok(v.remove(pos).expr)
+}
+
+/// Coerce an expression into a simple identifier.
+fn expect_ident(expr: Expr) -> syn::Result<Ident> {
+    syn::parse2(expr.into_token_stream())
+}
+
+/// Coerce an expression into a simple keyword.
+fn expect_litbool(expr: Expr) -> syn::Result<LitBool> {
+    syn::parse2(expr.into_token_stream())
+}
+
+/// Parse either a single identifier (`foo`) or an array of identifiers (`[foo, bar, baz]`).
+fn parse_ident_or_array(input: ParseStream) -> syn::Result<Vec<Ident>> {
+    if !input.peek(token::Bracket) {
+        return Ok(vec![input.parse()?]);
+    }
+
+    parse_ident_array(input)
+}
+
+/// Parse an array of expressions.
+fn parse_expr_array(input: ParseStream) -> syn::Result<Vec<Expr>> {
+    let content;
+    let _ = bracketed!(content in input);
+    let fields = content.parse_terminated(Expr::parse, Token![,])?;
+    Ok(fields.into_iter().collect())
+}
+
+/// Parse an array of idents, e.g. `[foo, bar, baz]`.
+fn parse_ident_array(input: ParseStream) -> syn::Result<Vec<Ident>> {
+    let content;
+    let _ = bracketed!(content in input);
+    let fields = content.parse_terminated(Ident::parse, Token![,])?;
+    Ok(fields.into_iter().collect())
+}
+
+/// Parse an pattern of idents, specifically `(foo | bar | baz)`.
+fn parse_ident_pat(input: ParseStream) -> syn::Result<Vec<Ident>> {
+    if !input.peek2(Token![|]) {
+        return Ok(vec![input.parse()?]);
+    }
+
+    let fields = Punctuated::<Ident, Token![|]>::parse_separated_nonempty(input)?;
+    Ok(fields.into_iter().collect())
+}
+
+/// A mapping of attributes to identifiers (just a simplified `Expr`).
+///
+/// Expressed as:
+///
+/// ```ignore
+/// #[meta1]
+/// #[meta2]
+/// [foo, bar, baz]
+/// ```
+#[derive(Debug)]
+pub struct AttributeMap {
+    pub meta: Vec<Meta>,
+    pub names: Vec<Ident>,
+}
+
+impl Parse for AttributeMap {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let attrs = input.call(Attribute::parse_outer)?;
+
+        Ok(Self {
+            meta: attrs.into_iter().map(|a| a.meta).collect(),
+            names: parse_ident_array(input)?,
+        })
+    }
+}
diff --git a/library/compiler-builtins/crates/libm-macros/src/shared.rs b/library/compiler-builtins/crates/libm-macros/src/shared.rs
new file mode 100644
index 00000000000..1cefe4e8c7e
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/src/shared.rs
@@ -0,0 +1,590 @@
+/* List of all functions that is shared between `libm-macros` and `libm-test`. */
+
+use std::fmt;
+use std::sync::LazyLock;
+
+struct NestedOp {
+    float_ty: FloatTy,
+    rust_sig: Signature,
+    c_sig: Option<Signature>,
+    fn_list: &'static [&'static str],
+    public: bool,
+}
+
+/// We need a flat list to work with most of the time, but define things as a more convenient
+/// nested list.
+const ALL_OPERATIONS_NESTED: &[NestedOp] = &[
+    NestedOp {
+        // `fn(f16) -> f16`
+        float_ty: FloatTy::F16,
+        rust_sig: Signature {
+            args: &[Ty::F16],
+            returns: &[Ty::F16],
+        },
+        c_sig: None,
+        fn_list: &[
+            "ceilf16",
+            "fabsf16",
+            "floorf16",
+            "rintf16",
+            "roundevenf16",
+            "roundf16",
+            "sqrtf16",
+            "truncf16",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `fn(f32) -> f32`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32],
+            returns: &[Ty::F32],
+        },
+        c_sig: None,
+        fn_list: &[
+            "acosf",
+            "acoshf",
+            "asinf",
+            "asinhf",
+            "atanf",
+            "atanhf",
+            "cbrtf",
+            "ceilf",
+            "cosf",
+            "coshf",
+            "erfcf",
+            "erff",
+            "exp10f",
+            "exp2f",
+            "expf",
+            "expm1f",
+            "fabsf",
+            "floorf",
+            "j0f",
+            "j1f",
+            "lgammaf",
+            "log10f",
+            "log1pf",
+            "log2f",
+            "logf",
+            "rintf",
+            "roundevenf",
+            "roundf",
+            "sinf",
+            "sinhf",
+            "sqrtf",
+            "tanf",
+            "tanhf",
+            "tgammaf",
+            "truncf",
+            "y0f",
+            "y1f",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `(f64) -> f64`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64],
+            returns: &[Ty::F64],
+        },
+        c_sig: None,
+        fn_list: &[
+            "acos",
+            "acosh",
+            "asin",
+            "asinh",
+            "atan",
+            "atanh",
+            "cbrt",
+            "ceil",
+            "cos",
+            "cosh",
+            "erf",
+            "erfc",
+            "exp",
+            "exp10",
+            "exp2",
+            "expm1",
+            "fabs",
+            "floor",
+            "j0",
+            "j1",
+            "lgamma",
+            "log",
+            "log10",
+            "log1p",
+            "log2",
+            "rint",
+            "round",
+            "roundeven",
+            "sin",
+            "sinh",
+            "sqrt",
+            "tan",
+            "tanh",
+            "tgamma",
+            "trunc",
+            "y0",
+            "y1",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `fn(f128) -> f128`
+        float_ty: FloatTy::F128,
+        rust_sig: Signature {
+            args: &[Ty::F128],
+            returns: &[Ty::F128],
+        },
+        c_sig: None,
+        fn_list: &[
+            "ceilf128",
+            "fabsf128",
+            "floorf128",
+            "rintf128",
+            "roundevenf128",
+            "roundf128",
+            "sqrtf128",
+            "truncf128",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `(f16, f16) -> f16`
+        float_ty: FloatTy::F16,
+        rust_sig: Signature {
+            args: &[Ty::F16, Ty::F16],
+            returns: &[Ty::F16],
+        },
+        c_sig: None,
+        fn_list: &[
+            "copysignf16",
+            "fdimf16",
+            "fmaxf16",
+            "fmaximum_numf16",
+            "fmaximumf16",
+            "fminf16",
+            "fminimum_numf16",
+            "fminimumf16",
+            "fmodf16",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `(f32, f32) -> f32`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32, Ty::F32],
+            returns: &[Ty::F32],
+        },
+        c_sig: None,
+        fn_list: &[
+            "atan2f",
+            "copysignf",
+            "fdimf",
+            "fmaxf",
+            "fmaximum_numf",
+            "fmaximumf",
+            "fminf",
+            "fminimum_numf",
+            "fminimumf",
+            "fmodf",
+            "hypotf",
+            "nextafterf",
+            "powf",
+            "remainderf",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `(f64, f64) -> f64`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64, Ty::F64],
+            returns: &[Ty::F64],
+        },
+        c_sig: None,
+        fn_list: &[
+            "atan2",
+            "copysign",
+            "fdim",
+            "fmax",
+            "fmaximum",
+            "fmaximum_num",
+            "fmin",
+            "fminimum",
+            "fminimum_num",
+            "fmod",
+            "hypot",
+            "nextafter",
+            "pow",
+            "remainder",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `(f128, f128) -> f128`
+        float_ty: FloatTy::F128,
+        rust_sig: Signature {
+            args: &[Ty::F128, Ty::F128],
+            returns: &[Ty::F128],
+        },
+        c_sig: None,
+        fn_list: &[
+            "copysignf128",
+            "fdimf128",
+            "fmaxf128",
+            "fmaximum_numf128",
+            "fmaximumf128",
+            "fminf128",
+            "fminimum_numf128",
+            "fminimumf128",
+            "fmodf128",
+        ],
+        public: true,
+    },
+    NestedOp {
+        // `(f32, f32, f32) -> f32`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32, Ty::F32, Ty::F32],
+            returns: &[Ty::F32],
+        },
+        c_sig: None,
+        fn_list: &["fmaf"],
+        public: true,
+    },
+    NestedOp {
+        // `(f64, f64, f64) -> f64`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64, Ty::F64, Ty::F64],
+            returns: &[Ty::F64],
+        },
+        c_sig: None,
+        fn_list: &["fma"],
+        public: true,
+    },
+    NestedOp {
+        // `(f128, f128, f128) -> f128`
+        float_ty: FloatTy::F128,
+        rust_sig: Signature {
+            args: &[Ty::F128, Ty::F128, Ty::F128],
+            returns: &[Ty::F128],
+        },
+        c_sig: None,
+        fn_list: &["fmaf128"],
+        public: true,
+    },
+    NestedOp {
+        // `(f32) -> i32`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32],
+            returns: &[Ty::I32],
+        },
+        c_sig: None,
+        fn_list: &["ilogbf"],
+        public: true,
+    },
+    NestedOp {
+        // `(f64) -> i32`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64],
+            returns: &[Ty::I32],
+        },
+        c_sig: None,
+        fn_list: &["ilogb"],
+        public: true,
+    },
+    NestedOp {
+        // `(i32, f32) -> f32`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::I32, Ty::F32],
+            returns: &[Ty::F32],
+        },
+        c_sig: None,
+        fn_list: &["jnf", "ynf"],
+        public: true,
+    },
+    NestedOp {
+        // `(i32, f64) -> f64`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::I32, Ty::F64],
+            returns: &[Ty::F64],
+        },
+        c_sig: None,
+        fn_list: &["jn", "yn"],
+        public: true,
+    },
+    NestedOp {
+        // `(f16, i32) -> f16`
+        float_ty: FloatTy::F16,
+        rust_sig: Signature {
+            args: &[Ty::F16, Ty::I32],
+            returns: &[Ty::F16],
+        },
+        c_sig: None,
+        fn_list: &["ldexpf16", "scalbnf16"],
+        public: true,
+    },
+    NestedOp {
+        // `(f32, i32) -> f32`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32, Ty::I32],
+            returns: &[Ty::F32],
+        },
+        c_sig: None,
+        fn_list: &["ldexpf", "scalbnf"],
+        public: true,
+    },
+    NestedOp {
+        // `(f64, i64) -> f64`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64, Ty::I32],
+            returns: &[Ty::F64],
+        },
+        c_sig: None,
+        fn_list: &["ldexp", "scalbn"],
+        public: true,
+    },
+    NestedOp {
+        // `(f128, i32) -> f128`
+        float_ty: FloatTy::F128,
+        rust_sig: Signature {
+            args: &[Ty::F128, Ty::I32],
+            returns: &[Ty::F128],
+        },
+        c_sig: None,
+        fn_list: &["ldexpf128", "scalbnf128"],
+        public: true,
+    },
+    NestedOp {
+        // `(f32, &mut f32) -> f32` as `(f32) -> (f32, f32)`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32],
+            returns: &[Ty::F32, Ty::F32],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F32, Ty::MutF32],
+            returns: &[Ty::F32],
+        }),
+        fn_list: &["modff"],
+        public: true,
+    },
+    NestedOp {
+        // `(f64, &mut f64) -> f64` as  `(f64) -> (f64, f64)`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64],
+            returns: &[Ty::F64, Ty::F64],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F64, Ty::MutF64],
+            returns: &[Ty::F64],
+        }),
+        fn_list: &["modf"],
+        public: true,
+    },
+    NestedOp {
+        // `(f32, &mut c_int) -> f32` as `(f32) -> (f32, i32)`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32],
+            returns: &[Ty::F32, Ty::I32],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F32, Ty::MutCInt],
+            returns: &[Ty::F32],
+        }),
+        fn_list: &["frexpf", "lgammaf_r"],
+        public: true,
+    },
+    NestedOp {
+        // `(f64, &mut c_int) -> f64` as `(f64) -> (f64, i32)`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64],
+            returns: &[Ty::F64, Ty::I32],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F64, Ty::MutCInt],
+            returns: &[Ty::F64],
+        }),
+        fn_list: &["frexp", "lgamma_r"],
+        public: true,
+    },
+    NestedOp {
+        // `(f32, f32, &mut c_int) -> f32` as `(f32, f32) -> (f32, i32)`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32, Ty::F32],
+            returns: &[Ty::F32, Ty::I32],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F32, Ty::F32, Ty::MutCInt],
+            returns: &[Ty::F32],
+        }),
+        fn_list: &["remquof"],
+        public: true,
+    },
+    NestedOp {
+        // `(f64, f64, &mut c_int) -> f64` as `(f64, f64) -> (f64, i32)`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64, Ty::F64],
+            returns: &[Ty::F64, Ty::I32],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F64, Ty::F64, Ty::MutCInt],
+            returns: &[Ty::F64],
+        }),
+        fn_list: &["remquo"],
+        public: true,
+    },
+    NestedOp {
+        // `(f32, &mut f32, &mut f32)` as `(f32) -> (f32, f32)`
+        float_ty: FloatTy::F32,
+        rust_sig: Signature {
+            args: &[Ty::F32],
+            returns: &[Ty::F32, Ty::F32],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F32, Ty::MutF32, Ty::MutF32],
+            returns: &[],
+        }),
+        fn_list: &["sincosf"],
+        public: true,
+    },
+    NestedOp {
+        // `(f64, &mut f64, &mut f64)` as `(f64) -> (f64, f64)`
+        float_ty: FloatTy::F64,
+        rust_sig: Signature {
+            args: &[Ty::F64],
+            returns: &[Ty::F64, Ty::F64],
+        },
+        c_sig: Some(Signature {
+            args: &[Ty::F64, Ty::MutF64, Ty::MutF64],
+            returns: &[],
+        }),
+        fn_list: &["sincos"],
+        public: true,
+    },
+];
+
+/// A type used in a function signature.
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum Ty {
+    F16,
+    F32,
+    F64,
+    F128,
+    I32,
+    CInt,
+    MutF16,
+    MutF32,
+    MutF64,
+    MutF128,
+    MutI32,
+    MutCInt,
+}
+
+/// A subset of [`Ty`] representing only floats.
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum FloatTy {
+    F16,
+    F32,
+    F64,
+    F128,
+}
+
+impl fmt::Display for Ty {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let s = match self {
+            Ty::F16 => "f16",
+            Ty::F32 => "f32",
+            Ty::F64 => "f64",
+            Ty::F128 => "f128",
+            Ty::I32 => "i32",
+            Ty::CInt => "::core::ffi::c_int",
+            Ty::MutF16 => "&mut f16",
+            Ty::MutF32 => "&mut f32",
+            Ty::MutF64 => "&mut f64",
+            Ty::MutF128 => "&mut f128",
+            Ty::MutI32 => "&mut i32",
+            Ty::MutCInt => "&mut ::core::ffi::c_int",
+        };
+        f.write_str(s)
+    }
+}
+
+impl fmt::Display for FloatTy {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let s = match self {
+            FloatTy::F16 => "f16",
+            FloatTy::F32 => "f32",
+            FloatTy::F64 => "f64",
+            FloatTy::F128 => "f128",
+        };
+        f.write_str(s)
+    }
+}
+
+/// Representation of e.g. `(f32, f32) -> f32`
+#[derive(Debug, Clone)]
+pub struct Signature {
+    pub args: &'static [Ty],
+    pub returns: &'static [Ty],
+}
+
+/// Combined information about a function implementation.
+#[derive(Debug, Clone)]
+pub struct MathOpInfo {
+    pub name: &'static str,
+    pub float_ty: FloatTy,
+    /// Function signature for C implementations
+    pub c_sig: Signature,
+    /// Function signature for Rust implementations
+    pub rust_sig: Signature,
+    /// True if part of libm's public API
+    pub public: bool,
+}
+
+/// A flat representation of `ALL_FUNCTIONS`.
+pub static ALL_OPERATIONS: LazyLock<Vec<MathOpInfo>> = LazyLock::new(|| {
+    let mut ret = Vec::new();
+
+    for op in ALL_OPERATIONS_NESTED {
+        let fn_names = op.fn_list;
+        for name in fn_names {
+            let api = MathOpInfo {
+                name,
+                float_ty: op.float_ty,
+                rust_sig: op.rust_sig.clone(),
+                c_sig: op.c_sig.clone().unwrap_or_else(|| op.rust_sig.clone()),
+                public: op.public,
+            };
+            ret.push(api);
+        }
+
+        if !fn_names.is_sorted() {
+            let mut sorted = (*fn_names).to_owned();
+            sorted.sort_unstable();
+            panic!("names list is not sorted: {fn_names:?}\nExpected: {sorted:?}");
+        }
+    }
+
+    ret.sort_by_key(|item| item.name);
+    ret
+});
diff --git a/library/compiler-builtins/crates/libm-macros/tests/basic.rs b/library/compiler-builtins/crates/libm-macros/tests/basic.rs
new file mode 100644
index 00000000000..b4276262229
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/tests/basic.rs
@@ -0,0 +1,177 @@
+#![feature(f16)]
+#![feature(f128)]
+// `STATUS_DLL_NOT_FOUND` on i686 MinGW, not worth looking into.
+#![cfg(not(all(target_arch = "x86", target_os = "windows", target_env = "gnu")))]
+
+macro_rules! basic {
+    (
+        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,
+        public: $public:expr,
+        attrs: [$($attr:meta),*],
+        extra: [$($extra_tt:tt)*],
+        fn_extra: $fn_extra:expr,
+    ) => {
+        $(#[$attr])*
+        #[allow(dead_code)]
+        pub mod $fn_name {
+            type FTy= $FTy;
+            type CFnTy<'a> = $CFn;
+            type RustFnTy = $RustFn;
+            type RustArgsTy = $RustArgs;
+            type RustRetTy = $RustRet;
+            const PUBLIC: bool = $public;
+            const A: &[&str] = &[$($extra_tt)*];
+            fn foo(a: f32) -> f32 {
+                $fn_extra(a)
+            }
+        }
+    };
+}
+
+mod test_basic {
+    libm_macros::for_each_function! {
+        callback: basic,
+        emit_types: all,
+        skip: [sin, cos],
+        attributes: [
+            // just some random attributes
+            #[allow(clippy::pedantic)]
+            #[allow(dead_code)]
+            [sinf, cosf]
+        ],
+        extra: ["foo", "bar"],
+        fn_extra: match MACRO_FN_NAME {
+            sin => |x| x + 2.0,
+            cos | cosf => |x: f32| x.MACRO_FN_NAME_NORMALIZED(),
+            _ => |_x| 100.0
+        }
+    }
+}
+
+macro_rules! basic_no_extra {
+    (
+        fn_name: $fn_name:ident,
+        attrs: [$($attr:meta),*],
+    ) => {
+        $(#[$attr])*
+        mod $fn_name {}
+    };
+}
+
+mod test_basic_no_extra {
+    // Test with no extra, no skip, and no attributes
+    libm_macros::for_each_function! {
+        callback: basic_no_extra,
+    }
+}
+
+mod test_only {
+    // Test that only works
+    libm_macros::for_each_function! {
+        callback: basic_no_extra,
+        only: [sin, sinf],
+    }
+}
+
+macro_rules! specified_types {
+    (
+        fn_name: $fn_name:ident,
+        RustFn: $RustFn:ty,
+        RustArgs: $RustArgs:ty,
+        attrs: [$($attr:meta),*],
+    ) => {
+        $(#[$attr])*
+        #[allow(dead_code)]
+        mod $fn_name {
+            type RustFnTy = $RustFn;
+            type RustArgsTy = $RustArgs;
+        }
+    };
+}
+
+mod test_emit_types {
+    // Test that we can specify a couple types to emit
+    libm_macros::for_each_function! {
+        callback: specified_types,
+        emit_types: [RustFn, RustArgs],
+    }
+}
+
+#[test]
+fn test_skip_f16_f128() {
+    macro_rules! skip_f16_f128 {
+        (
+        fn_name: $fn_name:ident,
+        attrs: [$($attr:meta),*],
+        extra: $vec:ident,
+    ) => {
+            $vec.push(stringify!($fn_name));
+        };
+    }
+
+    let mut v = Vec::new();
+    // Test with no extra, no skip, and no attributes
+    libm_macros::for_each_function! {
+        callback: skip_f16_f128,
+        skip_f16_f128: true,
+        extra: v,
+    }
+
+    for name in v {
+        assert!(!name.contains("f16"), "{name}");
+        assert!(!name.contains("f128"), "{name}");
+    }
+}
+
+#[test]
+fn test_fn_extra_expansion() {
+    macro_rules! fn_extra_expansion {
+        (
+            fn_name: $fn_name:ident,
+            attrs: [$($attr:meta),*],
+            fn_extra: $vec:expr,
+        ) => {
+            $vec.push(stringify!($fn_name));
+        };
+    }
+
+    let mut vf16 = Vec::new();
+    let mut vf32 = Vec::new();
+    let mut vf64 = Vec::new();
+    let mut vf128 = Vec::new();
+
+    // Test with no extra, no skip, and no attributes
+    libm_macros::for_each_function! {
+        callback: fn_extra_expansion,
+        fn_extra: match MACRO_FN_NAME {
+            ALL_F16 => vf16,
+            ALL_F32 => vf32,
+            ALL_F64 => vf64,
+            ALL_F128 => vf128,
+        }
+    }
+
+    // Skip functions with a suffix after the type spec
+    vf16.retain(|name| !name.ends_with("_r"));
+    vf32.retain(|name| !name.ends_with("_r"));
+    vf64.retain(|name| !name.ends_with("_r"));
+    vf128.retain(|name| !name.ends_with("_r"));
+
+    for name in vf16 {
+        assert!(name.ends_with("f16"), "{name}");
+    }
+    for name in vf32 {
+        assert!(name.ends_with("f"), "{name}");
+    }
+    let _ = vf64;
+    for name in vf128 {
+        assert!(name.ends_with("f128"), "{name}");
+    }
+}
diff --git a/library/compiler-builtins/crates/libm-macros/tests/enum.rs b/library/compiler-builtins/crates/libm-macros/tests/enum.rs
new file mode 100644
index 00000000000..93e209a0dcc
--- /dev/null
+++ b/library/compiler-builtins/crates/libm-macros/tests/enum.rs
@@ -0,0 +1,38 @@
+#[libm_macros::function_enum(BaseName)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum Identifier {}
+
+#[libm_macros::base_name_enum]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum BaseName {}
+
+#[test]
+fn as_str() {
+    assert_eq!(Identifier::Sin.as_str(), "sin");
+    assert_eq!(Identifier::Sinf.as_str(), "sinf");
+}
+
+#[test]
+fn from_str() {
+    assert_eq!(Identifier::from_str("sin").unwrap(), Identifier::Sin);
+    assert_eq!(Identifier::from_str("sinf").unwrap(), Identifier::Sinf);
+}
+
+#[test]
+fn basename() {
+    assert_eq!(Identifier::Sin.base_name(), BaseName::Sin);
+    assert_eq!(Identifier::Sinf.base_name(), BaseName::Sin);
+}
+
+#[test]
+fn math_op() {
+    assert_eq!(Identifier::Sin.math_op().float_ty, FloatTy::F64);
+    assert_eq!(Identifier::Sinf.math_op().float_ty, FloatTy::F32);
+}
+
+// Replicate the structure that we have in `libm-test`
+mod op {
+    include!("../../libm-macros/src/shared.rs");
+}
+
+use op::FloatTy;
diff --git a/library/compiler-builtins/crates/musl-math-sys/Cargo.toml b/library/compiler-builtins/crates/musl-math-sys/Cargo.toml
new file mode 100644
index 00000000000..d3fb147e526
--- /dev/null
+++ b/library/compiler-builtins/crates/musl-math-sys/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "musl-math-sys"
+version = "0.1.0"
+edition = "2024"
+publish = false
+license = "MIT OR Apache-2.0"
+
+[dependencies]
+
+[dev-dependencies]
+libm = { path = "../../libm" }
+
+[build-dependencies]
+cc = "1.2.16"
diff --git a/library/compiler-builtins/crates/musl-math-sys/build.rs b/library/compiler-builtins/crates/musl-math-sys/build.rs
new file mode 100644
index 00000000000..b00dbc73e28
--- /dev/null
+++ b/library/compiler-builtins/crates/musl-math-sys/build.rs
@@ -0,0 +1,350 @@
+use std::collections::BTreeMap;
+use std::path::{Path, PathBuf};
+use std::process::{Command, Stdio};
+use std::{env, fs, str};
+
+/// Static library that will be built
+const LIB_NAME: &str = "musl_math_prefixed";
+
+/// Files that have more than one symbol. Map of file names to the symbols defined in that file.
+const MULTIPLE_SYMBOLS: &[(&str, &[&str])] = &[
+    (
+        "__invtrigl",
+        &["__invtrigl", "__invtrigl_R", "__pio2_hi", "__pio2_lo"],
+    ),
+    ("__polevll", &["__polevll", "__p1evll"]),
+    ("erf", &["erf", "erfc"]),
+    ("erff", &["erff", "erfcf"]),
+    ("erfl", &["erfl", "erfcl"]),
+    ("exp10", &["exp10", "pow10"]),
+    ("exp10f", &["exp10f", "pow10f"]),
+    ("exp10l", &["exp10l", "pow10l"]),
+    ("exp2f_data", &["exp2f_data", "__exp2f_data"]),
+    ("exp_data", &["exp_data", "__exp_data"]),
+    ("j0", &["j0", "y0"]),
+    ("j0f", &["j0f", "y0f"]),
+    ("j1", &["j1", "y1"]),
+    ("j1f", &["j1f", "y1f"]),
+    ("jn", &["jn", "yn"]),
+    ("jnf", &["jnf", "ynf"]),
+    ("lgamma", &["lgamma", "__lgamma_r"]),
+    ("remainder", &["remainder", "drem"]),
+    ("remainderf", &["remainderf", "dremf"]),
+    ("lgammaf", &["lgammaf", "lgammaf_r", "__lgammaf_r"]),
+    ("lgammal", &["lgammal", "lgammal_r", "__lgammal_r"]),
+    ("log2_data", &["log2_data", "__log2_data"]),
+    ("log2f_data", &["log2f_data", "__log2f_data"]),
+    ("log_data", &["log_data", "__log_data"]),
+    ("logf_data", &["logf_data", "__logf_data"]),
+    ("pow_data", &["pow_data", "__pow_log_data"]),
+    ("powf_data", &["powf_data", "__powf_log2_data"]),
+    ("signgam", &["signgam", "__signgam"]),
+    ("sqrt_data", &["sqrt_data", "__rsqrt_tab"]),
+];
+
+fn main() {
+    let cfg = Config::from_env();
+
+    if cfg.target_env == "msvc"
+        || cfg.target_family == "wasm"
+        || cfg.target_features.iter().any(|f| f == "thumb-mode")
+    {
+        println!(
+            "cargo::warning=Musl doesn't compile with the current \
+            target {}; skipping build",
+            &cfg.target_string
+        );
+        return;
+    }
+
+    build_musl_math(&cfg);
+}
+
+#[allow(dead_code)]
+#[derive(Debug)]
+struct Config {
+    manifest_dir: PathBuf,
+    out_dir: PathBuf,
+    musl_dir: PathBuf,
+    musl_arch: String,
+    target_arch: String,
+    target_env: String,
+    target_family: String,
+    target_os: String,
+    target_string: String,
+    target_vendor: String,
+    target_features: Vec<String>,
+}
+
+impl Config {
+    fn from_env() -> Self {
+        let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+        let target_features = env::var("CARGO_CFG_TARGET_FEATURE")
+            .map(|feats| feats.split(',').map(ToOwned::to_owned).collect())
+            .unwrap_or_default();
+        let musl_dir = manifest_dir.join("musl");
+
+        let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
+        let musl_arch = if target_arch == "x86" {
+            "i386".to_owned()
+        } else {
+            target_arch.clone()
+        };
+
+        println!(
+            "cargo::rerun-if-changed={}/c_patches",
+            manifest_dir.display()
+        );
+        println!("cargo::rerun-if-changed={}", musl_dir.display());
+
+        Self {
+            manifest_dir,
+            out_dir: PathBuf::from(env::var("OUT_DIR").unwrap()),
+            musl_dir,
+            musl_arch,
+            target_arch,
+            target_env: env::var("CARGO_CFG_TARGET_ENV").unwrap(),
+            target_family: env::var("CARGO_CFG_TARGET_FAMILY").unwrap(),
+            target_os: env::var("CARGO_CFG_TARGET_OS").unwrap(),
+            target_string: env::var("TARGET").unwrap(),
+            target_vendor: env::var("CARGO_CFG_TARGET_VENDOR").unwrap(),
+            target_features,
+        }
+    }
+}
+
+/// Build musl math symbols to a static library
+fn build_musl_math(cfg: &Config) {
+    let musl_dir = &cfg.musl_dir;
+    let math = musl_dir.join("src/math");
+    let arch_dir = musl_dir.join("arch").join(&cfg.musl_arch);
+    assert!(
+        math.exists(),
+        "musl source not found. Is the submodule up to date?"
+    );
+
+    let source_map = find_math_source(&math, cfg);
+    let out_path = cfg.out_dir.join(format!("lib{LIB_NAME}.a"));
+
+    // Run configuration steps. Usually done as part of the musl `Makefile`.
+    let obj_include = cfg.out_dir.join("musl_obj/include");
+    fs::create_dir_all(&obj_include).unwrap();
+    fs::create_dir_all(obj_include.join("bits")).unwrap();
+    let sed_stat = Command::new("sed")
+        .arg("-f")
+        .arg(musl_dir.join("tools/mkalltypes.sed"))
+        .arg(arch_dir.join("bits/alltypes.h.in"))
+        .arg(musl_dir.join("include/alltypes.h.in"))
+        .stderr(Stdio::inherit())
+        .output()
+        .unwrap();
+    assert!(
+        sed_stat.status.success(),
+        "sed command failed: {:?}",
+        sed_stat.status
+    );
+
+    fs::write(obj_include.join("bits/alltypes.h"), sed_stat.stdout).unwrap();
+
+    let mut cbuild = cc::Build::new();
+    cbuild
+        .extra_warnings(false)
+        .warnings(false)
+        .flag_if_supported("-Wno-bitwise-op-parentheses")
+        .flag_if_supported("-Wno-literal-range")
+        .flag_if_supported("-Wno-parentheses")
+        .flag_if_supported("-Wno-shift-count-overflow")
+        .flag_if_supported("-Wno-shift-op-parentheses")
+        .flag_if_supported("-Wno-unused-but-set-variable")
+        .flag_if_supported("-std=c99")
+        .flag_if_supported("-ffreestanding")
+        .flag_if_supported("-nostdinc")
+        .define("_ALL_SOURCE", "1")
+        .define(
+            "ROOT_INCLUDE_FEATURES",
+            Some(musl_dir.join("include/features.h").to_str().unwrap()),
+        )
+        // Our overrides are in this directory
+        .include(cfg.manifest_dir.join("c_patches"))
+        .include(musl_dir.join("arch").join(&cfg.musl_arch))
+        .include(musl_dir.join("arch/generic"))
+        .include(musl_dir.join("src/include"))
+        .include(musl_dir.join("src/internal"))
+        .include(obj_include)
+        .include(musl_dir.join("include"))
+        .file(cfg.manifest_dir.join("c_patches/alias.c"));
+
+    for (sym_name, src_file) in source_map {
+        // Build the source file
+        cbuild.file(src_file);
+
+        // Trickery! Redefine the symbol names to have the prefix `musl_`, which allows us to
+        // differentiate these symbols from whatever we provide.
+        if let Some((_names, syms)) = MULTIPLE_SYMBOLS
+            .iter()
+            .find(|(name, _syms)| *name == sym_name)
+        {
+            // Handle the occasional file that defines multiple symbols
+            for sym in *syms {
+                cbuild.define(sym, Some(format!("musl_{sym}").as_str()));
+            }
+        } else {
+            // If the file doesn't define multiple symbols, the file name will be the symbol
+            cbuild.define(&sym_name, Some(format!("musl_{sym_name}").as_str()));
+        }
+    }
+
+    if cfg!(windows) {
+        // On Windows we don't have a good way to check symbols, so skip that step.
+        cbuild.compile(LIB_NAME);
+        return;
+    }
+
+    let objfiles = cbuild.compile_intermediates();
+
+    // We create the archive ourselves with relocations rather than letting `cc` do it so we can
+    // encourage it to resolve symbols now. This should help avoid accidentally linking the wrong
+    // thing.
+    let stat = cbuild
+        .get_compiler()
+        .to_command()
+        .arg("-r")
+        .arg("-o")
+        .arg(&out_path)
+        .args(objfiles)
+        .status()
+        .unwrap();
+    assert!(stat.success());
+
+    println!("cargo::rustc-link-lib={LIB_NAME}");
+    println!("cargo::rustc-link-search=native={}", cfg.out_dir.display());
+
+    validate_archive_symbols(&out_path);
+}
+
+/// Build a map of `name -> path`. `name` is typically the symbol name, but this doesn't account
+/// for files that provide multiple symbols.
+fn find_math_source(math_root: &Path, cfg: &Config) -> BTreeMap<String, PathBuf> {
+    let mut map = BTreeMap::new();
+    let mut arch_dir = None;
+
+    // Locate all files and directories
+    for item in fs::read_dir(math_root).unwrap() {
+        let path = item.unwrap().path();
+        let meta = fs::metadata(&path).unwrap();
+
+        if meta.is_dir() {
+            // Make note of the arch-specific directory if it exists
+            if path.file_name().unwrap() == cfg.target_arch.as_str() {
+                arch_dir = Some(path);
+            }
+            continue;
+        }
+
+        // Skip non-source files
+        if path.extension().is_some_and(|ext| ext == "h") {
+            continue;
+        }
+
+        let sym_name = path.file_stem().unwrap();
+        map.insert(sym_name.to_str().unwrap().to_owned(), path.to_owned());
+    }
+
+    // If arch-specific versions are available, build those instead.
+    if let Some(arch_dir) = arch_dir {
+        for item in fs::read_dir(arch_dir).unwrap() {
+            let path = item.unwrap().path();
+            let sym_name = path.file_stem().unwrap();
+
+            if path.extension().unwrap() == "s" {
+                // FIXME: we never build assembly versions since we have no good way to
+                // rename the symbol (our options are probably preprocessor or objcopy).
+                continue;
+            }
+            map.insert(sym_name.to_str().unwrap().to_owned(), path);
+        }
+    }
+
+    map
+}
+
+/// Make sure we don't have something like a loose unprefixed `_cos` called somewhere, which could
+/// wind up linking to system libraries rather than the built musl library.
+fn validate_archive_symbols(out_path: &Path) {
+    const ALLOWED_UNDEF_PFX: &[&str] = &[
+        // PIC and arch-specific
+        ".TOC",
+        "_GLOBAL_OFFSET_TABLE_",
+        "__x86.get_pc_thunk",
+        // gcc/compiler-rt/compiler-builtins symbols
+        "__add",
+        "__aeabi_",
+        "__div",
+        "__eq",
+        "__extend",
+        "__fix",
+        "__float",
+        "__gcc_",
+        "__ge",
+        "__gt",
+        "__le",
+        "__lshr",
+        "__lt",
+        "__mul",
+        "__ne",
+        "__stack_chk_fail",
+        "__stack_chk_guard",
+        "__sub",
+        "__trunc",
+        "__undef",
+        // string routines
+        "__bzero",
+        "bzero",
+        // FPENV interfaces
+        "feclearexcept",
+        "fegetround",
+        "feraiseexcept",
+        "fesetround",
+        "fetestexcept",
+    ];
+
+    // List global undefined symbols
+    let out = Command::new("nm")
+        .arg("-guj")
+        .arg(out_path)
+        .stderr(Stdio::inherit())
+        .output()
+        .unwrap();
+
+    let undef = str::from_utf8(&out.stdout).unwrap();
+    let mut undef = undef.lines().collect::<Vec<_>>();
+    undef.retain(|sym| {
+        // Account for file formats that add a leading `_`
+        !ALLOWED_UNDEF_PFX
+            .iter()
+            .any(|pfx| sym.starts_with(pfx) || sym[1..].starts_with(pfx))
+    });
+
+    assert!(
+        undef.is_empty(),
+        "found disallowed undefined symbols: {undef:#?}"
+    );
+
+    // Find any symbols that are missing the `_musl_` prefix`
+    let out = Command::new("nm")
+        .arg("-gUj")
+        .arg(out_path)
+        .stderr(Stdio::inherit())
+        .output()
+        .unwrap();
+
+    let defined = str::from_utf8(&out.stdout).unwrap();
+    let mut defined = defined.lines().collect::<Vec<_>>();
+    defined.retain(|sym| {
+        !(sym.starts_with("_musl_")
+            || sym.starts_with("musl_")
+            || sym.starts_with("__x86.get_pc_thunk"))
+    });
+
+    assert!(defined.is_empty(), "found unprefixed symbols: {defined:#?}");
+}
diff --git a/library/compiler-builtins/crates/musl-math-sys/c_patches/alias.c b/library/compiler-builtins/crates/musl-math-sys/c_patches/alias.c
new file mode 100644
index 00000000000..63e0f08d5eb
--- /dev/null
+++ b/library/compiler-builtins/crates/musl-math-sys/c_patches/alias.c
@@ -0,0 +1,40 @@
+/* On platforms that don't support weak symbols, define required aliases
+ * as wrappers. See comments in `features.h` for more.
+ */
+#if defined(__APPLE__) || defined(__MINGW32__)
+
+double __lgamma_r(double a, int *b);
+float __lgammaf_r(float a, int *b);
+long __lgammal_r(long double a, int *b);
+double exp10(double a);
+float exp10f(float a);
+long exp10l(long double a);
+double remainder(double a, double b);
+float remainderf(float a, float b);
+
+double lgamma_r(double a, int *b) {
+	return __lgamma_r(a, b);
+}
+float lgammaf_r(float a, int *b) {
+	return __lgammaf_r(a, b);
+}
+long double lgammal_r(long double a, int *b) {
+	return __lgammal_r(a, b);
+}
+double pow10(double a) {
+	return exp10(a);
+}
+float pow10f(float a) {
+	return exp10f(a);
+}
+long double pow10l(long double a) {
+	return exp10l(a);
+}
+double drem(double a, double b) {
+	return remainder(a, b);
+}
+float dremf(float a, float b) {
+	return remainderf(a, b);
+}
+
+#endif
diff --git a/library/compiler-builtins/crates/musl-math-sys/c_patches/features.h b/library/compiler-builtins/crates/musl-math-sys/c_patches/features.h
new file mode 100644
index 00000000000..97af935979a
--- /dev/null
+++ b/library/compiler-builtins/crates/musl-math-sys/c_patches/features.h
@@ -0,0 +1,39 @@
+/* This is meant to override Musl's src/include/features.h
+ *
+ * We use a separate file here to redefine some attributes that don't work on
+ * all platforms that we would like to build on.
+ */
+
+#ifndef FEATURES_H
+#define FEATURES_H
+
+/* Get the required `#include "../../include/features.h"` since we can't use
+ * the relative path. The C macros need double indirection to get a usable
+ * string. */
+#define _stringify_inner(s) #s
+#define _stringify(s) _stringify_inner(s)
+#include _stringify(ROOT_INCLUDE_FEATURES)
+
+#if defined(__APPLE__)
+#define weak __attribute__((__weak__))
+#define hidden __attribute__((__visibility__("hidden")))
+
+/* We _should_ be able to define this as:
+ *     _Pragma(_stringify(weak musl_ ## new = musl_ ## old))
+ * However, weak symbols aren't handled correctly [1]. So we manually write
+ * wrappers, which are in `alias.c`.
+ *
+ * [1]: https://github.com/llvm/llvm-project/issues/111321
+ */
+#define weak_alias(old, new) /* nothing */
+
+#else
+#define weak __attribute__((__weak__))
+#define hidden __attribute__((__visibility__("hidden")))
+#define weak_alias(old, new) \
+	extern __typeof(old) musl_ ## new \
+	__attribute__((__weak__, __alias__(_stringify(musl_ ## old))))
+
+#endif /* defined(__APPLE__) */
+
+#endif
diff --git a/library/compiler-builtins/crates/musl-math-sys/musl b/library/compiler-builtins/crates/musl-math-sys/musl
new file mode 160000
+Subproject c47ad25ea3b484e10326f933e927c0bc8cded3d
diff --git a/library/compiler-builtins/crates/musl-math-sys/src/lib.rs b/library/compiler-builtins/crates/musl-math-sys/src/lib.rs
new file mode 100644
index 00000000000..6a4bf4859d9
--- /dev/null
+++ b/library/compiler-builtins/crates/musl-math-sys/src/lib.rs
@@ -0,0 +1,287 @@
+//! Bindings to Musl math functions (these are built in `build.rs`).
+
+use std::ffi::{c_char, c_int, c_long};
+
+/// Macro for creating bindings and exposing a safe function (since the implementations have no
+/// preconditions). Included functions must have correct signatures, otherwise this will be
+/// unsound.
+macro_rules! functions {
+    ( $(
+        $( #[$meta:meta] )*
+        $pfx_name:ident: $name:ident( $($arg:ident: $aty:ty),+ ) -> $rty:ty;
+    )* ) => {
+        unsafe extern "C" {
+            $( fn $pfx_name( $($arg: $aty),+ ) -> $rty; )*
+        }
+
+        $(
+            // Expose a safe version
+            $( #[$meta] )*
+            pub fn $name( $($arg: $aty),+ ) -> $rty {
+                // SAFETY: FFI calls with no preconditions
+                unsafe { $pfx_name( $($arg),+ ) }
+            }
+        )*
+
+        #[cfg(test)]
+        mod tests {
+            use super::*;
+            use test_support::CallTest;
+
+            $( functions!(
+                @single_test
+                $name($($arg: $aty),+) -> $rty
+            ); )*
+        }
+    };
+
+    (@single_test
+        $name:ident( $($arg:ident: $aty:ty),+ ) -> $rty:ty
+    ) => {
+        // Run a simple check to ensure we can link and call the function without crashing.
+        #[test]
+        // FIXME(#309): LE PPC crashes calling some musl functions
+        #[cfg_attr(all(target_arch = "powerpc64", target_endian = "little"), ignore)]
+        fn $name() {
+            <fn($($aty),+) -> $rty>::check(super::$name);
+        }
+    };
+}
+
+#[cfg(test)]
+mod test_support {
+    use core::ffi::c_char;
+
+    /// Just verify that we are able to call the function.
+    pub trait CallTest {
+        fn check(f: Self);
+    }
+
+    macro_rules! impl_calltest {
+        ($( ($($arg:ty),*) -> $ret:ty; )*) => {
+            $(
+                impl CallTest for fn($($arg),*) -> $ret {
+                    fn check(f: Self) {
+                        f($(1 as $arg),*);
+                    }
+                }
+            )*
+        };
+    }
+
+    impl_calltest! {
+        (f32) -> f32;
+        (f64) -> f64;
+        (f32, f32) -> f32;
+        (f64, f64) -> f64;
+        (i32, f32) -> f32;
+        (i32, f64) -> f64;
+        (f32, f32, f32) -> f32;
+        (f64, f64, f64) -> f64;
+        (f32, i32) -> f32;
+        (f32, i64) -> f32;
+        (f32) -> i32;
+        (f64) -> i32;
+        (f64, i32) -> f64;
+        (f64, i64) -> f64;
+    }
+
+    impl CallTest for fn(f32, &mut f32) -> f32 {
+        fn check(f: Self) {
+            let mut tmp = 0.0;
+            f(0.0, &mut tmp);
+        }
+    }
+    impl CallTest for fn(f64, &mut f64) -> f64 {
+        fn check(f: Self) {
+            let mut tmp = 0.0;
+            f(0.0, &mut tmp);
+        }
+    }
+    impl CallTest for fn(f32, &mut i32) -> f32 {
+        fn check(f: Self) {
+            let mut tmp = 1;
+            f(0.0, &mut tmp);
+        }
+    }
+    impl CallTest for fn(f64, &mut i32) -> f64 {
+        fn check(f: Self) {
+            let mut tmp = 1;
+            f(0.0, &mut tmp);
+        }
+    }
+    impl CallTest for fn(f32, f32, &mut i32) -> f32 {
+        fn check(f: Self) {
+            let mut tmp = 1;
+            f(0.0, 0.0, &mut tmp);
+        }
+    }
+    impl CallTest for fn(f64, f64, &mut i32) -> f64 {
+        fn check(f: Self) {
+            let mut tmp = 1;
+            f(0.0, 0.0, &mut tmp);
+        }
+    }
+    impl CallTest for fn(f32, &mut f32, &mut f32) {
+        fn check(f: Self) {
+            let mut tmp1 = 1.0;
+            let mut tmp2 = 1.0;
+            f(0.0, &mut tmp1, &mut tmp2);
+        }
+    }
+    impl CallTest for fn(f64, &mut f64, &mut f64) {
+        fn check(f: Self) {
+            let mut tmp1 = 1.0;
+            let mut tmp2 = 1.0;
+            f(0.0, &mut tmp1, &mut tmp2);
+        }
+    }
+    impl CallTest for fn(*const c_char) -> f32 {
+        fn check(f: Self) {
+            f(c"1".as_ptr());
+        }
+    }
+    impl CallTest for fn(*const c_char) -> f64 {
+        fn check(f: Self) {
+            f(c"1".as_ptr());
+        }
+    }
+}
+
+functions! {
+    musl_acos: acos(a: f64) -> f64;
+    musl_acosf: acosf(a: f32) -> f32;
+    musl_acosh: acosh(a: f64) -> f64;
+    musl_acoshf: acoshf(a: f32) -> f32;
+    musl_asin: asin(a: f64) -> f64;
+    musl_asinf: asinf(a: f32) -> f32;
+    musl_asinh: asinh(a: f64) -> f64;
+    musl_asinhf: asinhf(a: f32) -> f32;
+    musl_atan2: atan2(a: f64, b: f64) -> f64;
+    musl_atan2f: atan2f(a: f32, b: f32) -> f32;
+    musl_atan: atan(a: f64) -> f64;
+    musl_atanf: atanf(a: f32) -> f32;
+    musl_atanh: atanh(a: f64) -> f64;
+    musl_atanhf: atanhf(a: f32) -> f32;
+    musl_cbrt: cbrt(a: f64) -> f64;
+    musl_cbrtf: cbrtf(a: f32) -> f32;
+    musl_ceil: ceil(a: f64) -> f64;
+    musl_ceilf: ceilf(a: f32) -> f32;
+    musl_copysign: copysign(a: f64, b: f64) -> f64;
+    musl_copysignf: copysignf(a: f32, b: f32) -> f32;
+    musl_cos: cos(a: f64) -> f64;
+    musl_cosf: cosf(a: f32) -> f32;
+    musl_cosh: cosh(a: f64) -> f64;
+    musl_coshf: coshf(a: f32) -> f32;
+    musl_drem: drem(a: f64, b: f64) -> f64;
+    musl_dremf: dremf(a: f32, b: f32) -> f32;
+    musl_erf: erf(a: f64) -> f64;
+    musl_erfc: erfc(a: f64) -> f64;
+    musl_erfcf: erfcf(a: f32) -> f32;
+    musl_erff: erff(a: f32) -> f32;
+    musl_exp10: exp10(a: f64) -> f64;
+    musl_exp10f: exp10f(a: f32) -> f32;
+    musl_exp2: exp2(a: f64) -> f64;
+    musl_exp2f: exp2f(a: f32) -> f32;
+    musl_exp: exp(a: f64) -> f64;
+    musl_expf: expf(a: f32) -> f32;
+    musl_expm1: expm1(a: f64) -> f64;
+    musl_expm1f: expm1f(a: f32) -> f32;
+    musl_fabs: fabs(a: f64) -> f64;
+    musl_fabsf: fabsf(a: f32) -> f32;
+    musl_fdim: fdim(a: f64, b: f64) -> f64;
+    musl_fdimf: fdimf(a: f32, b: f32) -> f32;
+    musl_finite: finite(a: f64) -> c_int;
+    musl_finitef: finitef(a: f32) -> c_int;
+    musl_floor: floor(a: f64) -> f64;
+    musl_floorf: floorf(a: f32) -> f32;
+    musl_fma: fma(a: f64, b: f64, c: f64) -> f64;
+    musl_fmaf: fmaf(a: f32, b: f32, c: f32) -> f32;
+    musl_fmax: fmax(a: f64, b: f64) -> f64;
+    musl_fmaxf: fmaxf(a: f32, b: f32) -> f32;
+    musl_fmin: fmin(a: f64, b: f64) -> f64;
+    musl_fminf: fminf(a: f32, b: f32) -> f32;
+    musl_fmod: fmod(a: f64, b: f64) -> f64;
+    musl_fmodf: fmodf(a: f32, b: f32) -> f32;
+    musl_frexp: frexp(a: f64, b: &mut c_int) -> f64;
+    musl_frexpf: frexpf(a: f32, b: &mut c_int) -> f32;
+    musl_hypot: hypot(a: f64, b: f64) -> f64;
+    musl_hypotf: hypotf(a: f32, b: f32) -> f32;
+    musl_ilogb: ilogb(a: f64) -> c_int;
+    musl_ilogbf: ilogbf(a: f32) -> c_int;
+    musl_j0: j0(a: f64) -> f64;
+    musl_j0f: j0f(a: f32) -> f32;
+    musl_j1: j1(a: f64) -> f64;
+    musl_j1f: j1f(a: f32) -> f32;
+    musl_jn: jn(a: c_int, b: f64) -> f64;
+    musl_jnf: jnf(a: c_int, b: f32) -> f32;
+    musl_ldexp: ldexp(a: f64, b: c_int) -> f64;
+    musl_ldexpf: ldexpf(a: f32, b: c_int) -> f32;
+    musl_lgamma: lgamma(a: f64) -> f64;
+    musl_lgamma_r: lgamma_r(a: f64, b: &mut c_int) -> f64;
+    musl_lgammaf: lgammaf(a: f32) -> f32;
+    musl_lgammaf_r: lgammaf_r(a: f32, b: &mut c_int) -> f32;
+    musl_log10: log10(a: f64) -> f64;
+    musl_log10f: log10f(a: f32) -> f32;
+    musl_log1p: log1p(a: f64) -> f64;
+    musl_log1pf: log1pf(a: f32) -> f32;
+    musl_log2: log2(a: f64) -> f64;
+    musl_log2f: log2f(a: f32) -> f32;
+    musl_log: log(a: f64) -> f64;
+    musl_logb: logb(a: f64) -> f64;
+    musl_logbf: logbf(a: f32) -> f32;
+    musl_logf: logf(a: f32) -> f32;
+    musl_modf: modf(a: f64, b: &mut f64) -> f64;
+    musl_modff: modff(a: f32, b: &mut f32) -> f32;
+
+    // FIXME: these need to be unsafe
+    #[allow(clippy::not_unsafe_ptr_arg_deref)]
+    musl_nan: nan(a: *const c_char) -> f64;
+    #[allow(clippy::not_unsafe_ptr_arg_deref)]
+    musl_nanf: nanf(a: *const c_char) -> f32;
+
+    musl_nearbyint: nearbyint(a: f64) -> f64;
+    musl_nearbyintf: nearbyintf(a: f32) -> f32;
+    musl_nextafter: nextafter(a: f64, b: f64) -> f64;
+    musl_nextafterf: nextafterf(a: f32, b: f32) -> f32;
+    musl_pow10: pow10(a: f64) -> f64;
+    musl_pow10f: pow10f(a: f32) -> f32;
+    musl_pow: pow(a: f64, b: f64) -> f64;
+    musl_powf: powf(a: f32, b: f32) -> f32;
+    musl_remainder: remainder(a: f64, b: f64) -> f64;
+    musl_remainderf: remainderf(a: f32, b: f32) -> f32;
+    musl_remquo: remquo(a: f64, b: f64, c: &mut c_int) -> f64;
+    musl_remquof: remquof(a: f32, b: f32, c: &mut c_int) -> f32;
+    musl_rint: rint(a: f64) -> f64;
+    musl_rintf: rintf(a: f32) -> f32;
+    musl_round: round(a: f64) -> f64;
+    musl_roundf: roundf(a: f32) -> f32;
+    musl_scalbln: scalbln(a: f64, b: c_long) -> f64;
+    musl_scalblnf: scalblnf(a: f32, b: c_long) -> f32;
+    musl_scalbn: scalbn(a: f64, b: c_int) -> f64;
+    musl_scalbnf: scalbnf(a: f32, b: c_int) -> f32;
+    musl_significand: significand(a: f64) -> f64;
+    musl_significandf: significandf(a: f32) -> f32;
+    musl_sin: sin(a: f64) -> f64;
+    musl_sincos: sincos(a: f64, b: &mut f64, c: &mut f64) -> ();
+    musl_sincosf: sincosf(a: f32, b: &mut f32, c: &mut f32) -> ();
+    musl_sinf: sinf(a: f32) -> f32;
+    musl_sinh: sinh(a: f64) -> f64;
+    musl_sinhf: sinhf(a: f32) -> f32;
+    musl_sqrt: sqrt(a: f64) -> f64;
+    musl_sqrtf: sqrtf(a: f32) -> f32;
+    musl_tan: tan(a: f64) -> f64;
+    musl_tanf: tanf(a: f32) -> f32;
+    musl_tanh: tanh(a: f64) -> f64;
+    musl_tanhf: tanhf(a: f32) -> f32;
+    musl_tgamma: tgamma(a: f64) -> f64;
+    musl_tgammaf: tgammaf(a: f32) -> f32;
+    musl_trunc: trunc(a: f64) -> f64;
+    musl_truncf: truncf(a: f32) -> f32;
+    musl_y0: y0(a: f64) -> f64;
+    musl_y0f: y0f(a: f32) -> f32;
+    musl_y1: y1(a: f64) -> f64;
+    musl_y1f: y1f(a: f32) -> f32;
+    musl_yn: yn(a: c_int, b: f64) -> f64;
+    musl_ynf: ynf(a: c_int, b: f32) -> f32;
+}
diff --git a/library/compiler-builtins/crates/panic-handler/Cargo.toml b/library/compiler-builtins/crates/panic-handler/Cargo.toml
new file mode 100644
index 00000000000..a6764fc481b
--- /dev/null
+++ b/library/compiler-builtins/crates/panic-handler/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "panic-handler"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2024"
+publish = false
+
+[lib]
+test = false
+bench = false
+
+[dependencies]
diff --git a/library/compiler-builtins/crates/panic-handler/src/lib.rs b/library/compiler-builtins/crates/panic-handler/src/lib.rs
new file mode 100644
index 00000000000..673e005224b
--- /dev/null
+++ b/library/compiler-builtins/crates/panic-handler/src/lib.rs
@@ -0,0 +1,11 @@
+//! This is needed for tests on targets that require a `#[panic_handler]` function
+
+#![feature(no_core)]
+#![no_core]
+
+extern crate core;
+
+#[panic_handler]
+fn panic(_: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
diff --git a/library/compiler-builtins/crates/util/Cargo.toml b/library/compiler-builtins/crates/util/Cargo.toml
new file mode 100644
index 00000000000..614c54bd835
--- /dev/null
+++ b/library/compiler-builtins/crates/util/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "util"
+version = "0.1.0"
+edition = "2024"
+publish = false
+license = "MIT OR Apache-2.0"
+
+[features]
+default = ["build-musl", "build-mpfr", "unstable-float"]
+build-musl = ["libm-test/build-musl", "dep:musl-math-sys"]
+build-mpfr = ["libm-test/build-mpfr", "dep:rug"]
+unstable-float = ["libm/unstable-float", "libm-test/unstable-float", "rug?/nightly-float"]
+
+[dependencies]
+libm = { path = "../../libm", default-features = false }
+libm-macros = { path = "../libm-macros" }
+libm-test = { path = "../../libm-test", default-features = false }
+musl-math-sys = { path = "../musl-math-sys", optional = true }
+rug = { version = "1.27.0", optional = true, default-features = false, features = ["float", "std"] }
diff --git a/library/compiler-builtins/crates/util/build.rs b/library/compiler-builtins/crates/util/build.rs
new file mode 100644
index 00000000000..a1be4127527
--- /dev/null
+++ b/library/compiler-builtins/crates/util/build.rs
@@ -0,0 +1,10 @@
+#![allow(unexpected_cfgs)]
+
+#[path = "../../libm/configure.rs"]
+mod configure;
+
+fn main() {
+    println!("cargo:rerun-if-changed=../../libm/configure.rs");
+    let cfg = configure::Config::from_env();
+    configure::emit_libm_config(&cfg);
+}
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
+    }
+}