Merge pull request rust-lang/libm#300 from tgross35/test-refactoring
Test more targets against a custom-built musl libm
This commit is contained in:
commit
73fd634908
32 changed files with 3008 additions and 45 deletions
|
|
@ -2,38 +2,103 @@ name: CI
|
|||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_VERBOSE: true
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: full
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
test:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
- arm-unknown-linux-gnueabi
|
||||
- arm-unknown-linux-gnueabihf
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
# - i686-unknown-linux-gnu
|
||||
# MIPS targets disabled since they are dropped to tier 3.
|
||||
# See https://github.com/rust-lang/compiler-team/issues/648
|
||||
#- mips-unknown-linux-gnu
|
||||
#- mips64-unknown-linux-gnuabi64
|
||||
#- mips64el-unknown-linux-gnuabi64
|
||||
- powerpc-unknown-linux-gnu
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- powerpc64le-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-gnu
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
build_only: 1 # Can't run on x86 hosts
|
||||
- target: arm-unknown-linux-gnueabi
|
||||
os: ubuntu-latest
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
- target: i586-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: i686-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: powerpc-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: powerpc64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: powerpc64le-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: thumbv6m-none-eabi
|
||||
os: ubuntu-latest
|
||||
- target: thumbv7em-none-eabi
|
||||
os: ubuntu-latest
|
||||
- target: thumbv7em-none-eabihf
|
||||
os: ubuntu-latest
|
||||
- target: thumbv7m-none-eabi
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-13
|
||||
- target: i686-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: i686-pc-windows-gnu
|
||||
os: windows-latest
|
||||
channel: nightly-i686-gnu
|
||||
- target: x86_64-pc-windows-gnu
|
||||
os: windows-latest
|
||||
channel: nightly-x86_64-gnu
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
BUILD_ONLY: ${{ matrix.build_only }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
run: rustup update nightly --no-self-update && rustup default nightly
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- run: rustup target add x86_64-unknown-linux-musl
|
||||
- run: cargo generate-lockfile
|
||||
- run: ./ci/run-docker.sh ${{ matrix.target }}
|
||||
- name: Print runner information
|
||||
run: uname -a
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust (rustup)
|
||||
shell: bash
|
||||
run: |
|
||||
channel="nightly"
|
||||
# Account for channels that have required components (MinGW)
|
||||
[ -n "${{ matrix.channel }}" ] && channel="${{ matrix.channel }}"
|
||||
rustup update "$channel" --no-self-update
|
||||
rustup default "$channel"
|
||||
rustup target add ${{ matrix.target }}
|
||||
rustup component add llvm-tools-preview
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.target }}
|
||||
|
||||
- name: Download musl source
|
||||
run: ./ci/download-musl.sh
|
||||
shell: bash
|
||||
|
||||
# Non-linux tests just use our raw script
|
||||
- name: Run locally
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
shell: bash
|
||||
run: ./ci/run.sh ${{ matrix.target }}
|
||||
|
||||
# Otherwise we use our docker containers to run builds
|
||||
- name: Run in Docker
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
cargo generate-lockfile && ./ci/run-docker.sh ${{ matrix.target }}
|
||||
|
||||
wasm:
|
||||
name: WebAssembly
|
||||
|
|
@ -45,7 +110,7 @@ jobs:
|
|||
- run: rustup target add wasm32-unknown-unknown
|
||||
- run: cargo build --target wasm32-unknown-unknown
|
||||
|
||||
cb:
|
||||
builtins:
|
||||
name: "The compiler-builtins crate works"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
@ -61,6 +126,8 @@ jobs:
|
|||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
run: rustup update nightly --no-self-update && rustup default nightly
|
||||
- name: Download musl source
|
||||
run: ./ci/download-musl.sh
|
||||
- run: cargo bench --all
|
||||
|
||||
msrv:
|
||||
|
|
@ -92,9 +159,9 @@ jobs:
|
|||
|
||||
success:
|
||||
needs:
|
||||
- docker
|
||||
- test
|
||||
- wasm
|
||||
- cb
|
||||
- builtins
|
||||
- benchmarks
|
||||
- msrv
|
||||
- rustfmt
|
||||
|
|
|
|||
5
library/compiler-builtins/libm/.gitignore
vendored
5
library/compiler-builtins/libm/.gitignore
vendored
|
|
@ -1,8 +1,9 @@
|
|||
**/*.rs.bk
|
||||
**.bk
|
||||
.#*
|
||||
/bin
|
||||
/math/src
|
||||
/math/target
|
||||
/target
|
||||
/tests
|
||||
Cargo.lock
|
||||
musl/
|
||||
**.tar.gz
|
||||
|
|
|
|||
|
|
@ -24,13 +24,17 @@ unstable = []
|
|||
force-soft-floats = []
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/compiler-builtins-smoke-test",
|
||||
"crates/libm-bench",
|
||||
"crates/libm-macros",
|
||||
"crates/libm-test",
|
||||
"crates/musl-math-sys",
|
||||
]
|
||||
default-members = [
|
||||
".",
|
||||
"crates/libm-macros",
|
||||
"crates/libm-test",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc-multilib libc6-dev ca-certificates
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc libc6-dev qemu-user-static ca-certificates \
|
||||
gcc-riscv64-linux-gnu libc6-dev-riscv64-cross \
|
||||
qemu-system-riscv64
|
||||
|
||||
ENV TOOLCHAIN_PREFIX=riscv64-linux-gnu-
|
||||
ENV CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER="$TOOLCHAIN_PREFIX"gcc \
|
||||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER=qemu-riscv64-static \
|
||||
AR_riscv64gc_unknown_linux_gnu="$TOOLCHAIN_PREFIX"ar \
|
||||
CC_riscv64gc_unknown_linux_gnu="$TOOLCHAIN_PREFIX"gcc \
|
||||
QEMU_LD_PREFIX=/usr/riscv64-linux-gnu \
|
||||
RUST_TEST_THREADS=1
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
ARG IMAGE=ubuntu:24.04
|
||||
FROM $IMAGE
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc libc6-dev ca-certificates \
|
||||
gcc-arm-none-eabi \
|
||||
libnewlib-arm-none-eabi
|
||||
ENV BUILD_ONLY=1
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
ARG IMAGE=ubuntu:24.04
|
||||
FROM $IMAGE
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc libc6-dev ca-certificates \
|
||||
gcc-arm-none-eabi \
|
||||
libnewlib-arm-none-eabi
|
||||
ENV BUILD_ONLY=1
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
ARG IMAGE=ubuntu:24.04
|
||||
FROM $IMAGE
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc libc6-dev ca-certificates \
|
||||
gcc-arm-none-eabi \
|
||||
libnewlib-arm-none-eabi
|
||||
ENV BUILD_ONLY=1
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
ARG IMAGE=ubuntu:24.04
|
||||
FROM $IMAGE
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc libc6-dev ca-certificates \
|
||||
gcc-arm-none-eabi \
|
||||
libnewlib-arm-none-eabi
|
||||
ENV BUILD_ONLY=1
|
||||
24
library/compiler-builtins/libm/ci/download-musl.sh
Executable file
24
library/compiler-builtins/libm/ci/download-musl.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
# Download the expected version of musl to a directory `musl`
|
||||
|
||||
set -eux
|
||||
|
||||
fname=musl-1.2.5.tar.gz
|
||||
sha=a9a118bbe84d8764da0ea0d28b3ab3fae8477fc7e4085d90102b8596fc7c75e4
|
||||
|
||||
mkdir musl
|
||||
curl "https://musl.libc.org/releases/$fname" -O
|
||||
|
||||
case "$(uname -s)" in
|
||||
MINGW*)
|
||||
# Need to extract the second line because certutil does human output
|
||||
fsha=$(certutil -hashfile "$fname" SHA256 | sed -n '2p')
|
||||
[ "$sha" = "$fsha" ] || exit 1
|
||||
;;
|
||||
*)
|
||||
echo "$sha $fname" | shasum -a 256 --check || exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
tar -xzf "$fname" -C musl --strip-components 1
|
||||
rm "$fname"
|
||||
|
|
@ -21,6 +21,7 @@ run() {
|
|||
-e RUSTFLAGS \
|
||||
-e CARGO_HOME=/cargo \
|
||||
-e CARGO_TARGET_DIR=/target \
|
||||
-e EMULATED=1 \
|
||||
-v "${HOME}/.cargo:/cargo" \
|
||||
-v "$(pwd)/target:/target" \
|
||||
-v "$(pwd):/checkout:ro" \
|
||||
|
|
|
|||
|
|
@ -2,21 +2,64 @@
|
|||
|
||||
set -eux
|
||||
|
||||
target="$1"
|
||||
|
||||
cmd="cargo test --all --target $target"
|
||||
|
||||
export RUST_BACKTRACE="${RUST_BACKTRACE:-full}"
|
||||
# Needed for no-panic to correct detect a lack of panics
|
||||
export RUSTFLAGS="$RUSTFLAGS -Ccodegen-units=1"
|
||||
export RUSTFLAGS="${RUSTFLAGS:-} -Ccodegen-units=1"
|
||||
|
||||
# stable by default
|
||||
$cmd
|
||||
$cmd --release
|
||||
target="${1:-}"
|
||||
|
||||
# unstable with a feature
|
||||
$cmd --features 'unstable'
|
||||
$cmd --release --features 'unstable'
|
||||
if [ -z "$target" ]; then
|
||||
host_target=$(rustc -vV | awk '/^host/ { print $2 }')
|
||||
echo "Defaulted to host target $host_target"
|
||||
target="$host_target"
|
||||
fi
|
||||
|
||||
# also run the reference tests
|
||||
$cmd --features 'unstable libm-test/test-musl-serialized'
|
||||
$cmd --release --features 'unstable libm-test/test-musl-serialized'
|
||||
extra_flags=""
|
||||
|
||||
# We need to specifically skip tests for musl-math-sys on systems that can't
|
||||
# build musl since otherwise `--all` will activate it.
|
||||
case "$target" in
|
||||
# Can't build at all on MSVC, WASM, or thumb
|
||||
*windows-msvc*) extra_flags="$extra_flags --exclude musl-math-sys" ;;
|
||||
*wasm*) extra_flags="$extra_flags --exclude musl-math-sys" ;;
|
||||
*thumb*) extra_flags="$extra_flags --exclude musl-math-sys" ;;
|
||||
|
||||
# We can build musl on MinGW but running tests gets a stack overflow
|
||||
*windows-gnu*) ;;
|
||||
# FIXME(#309): LE PPC crashes calling the musl version of some functions. It
|
||||
# seems like a qemu bug but should be investigated further at some point.
|
||||
# See <https://github.com/rust-lang/libm/issues/309>.
|
||||
*powerpc64le*) ;;
|
||||
|
||||
# Everything else gets musl enabled
|
||||
*) extra_flags="$extra_flags --features libm-test/build-musl" ;;
|
||||
esac
|
||||
|
||||
# FIXME: `STATUS_DLL_NOT_FOUND` testing macros on CI.
|
||||
# <https://github.com/rust-lang/rust/issues/128944>
|
||||
case "$target" in
|
||||
*windows-gnu) extra_flags="$extra_flags --exclude libm-macros" ;;
|
||||
esac
|
||||
|
||||
if [ "$(uname -a)" = "Linux" ]; then
|
||||
# also run the reference tests when we can. requires a Linux host.
|
||||
extra_flags="$extra_flags --features libm-test/test-musl-serialized"
|
||||
fi
|
||||
|
||||
if [ "${BUILD_ONLY:-}" = "1" ]; then
|
||||
cmd="cargo build --target $target --package libm"
|
||||
$cmd
|
||||
$cmd --features 'unstable'
|
||||
|
||||
echo "can't run tests on $target"
|
||||
else
|
||||
cmd="cargo test --all --target $target $extra_flags"
|
||||
|
||||
# stable by default
|
||||
$cmd
|
||||
$cmd --release
|
||||
|
||||
# unstable with a feature
|
||||
$cmd --features 'unstable'
|
||||
$cmd --release --features 'unstable'
|
||||
fi
|
||||
|
|
|
|||
12
library/compiler-builtins/libm/crates/libm-macros/Cargo.toml
Normal file
12
library/compiler-builtins/libm/crates/libm-macros/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "libm-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.88"
|
||||
quote = "1.0.37"
|
||||
syn = { version = "2.0.79", features = ["full", "extra-traits", "visit-mut"] }
|
||||
541
library/compiler-builtins/libm/crates/libm-macros/src/lib.rs
Normal file
541
library/compiler-builtins/libm/crates/libm-macros/src/lib.rs
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
mod parse;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use parse::{Invocation, StructuredInput};
|
||||
use proc_macro as pm;
|
||||
use proc_macro2::{self as pm2, Span};
|
||||
use quote::{ToTokens, quote};
|
||||
use syn::Ident;
|
||||
use syn::visit_mut::VisitMut;
|
||||
|
||||
const ALL_FUNCTIONS: &[(Signature, Option<Signature>, &[&str])] = &[
|
||||
(
|
||||
// `fn(f32) -> f32`
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&[
|
||||
"acosf", "acoshf", "asinf", "asinhf", "atanf", "atanhf", "cbrtf", "ceilf", "cosf",
|
||||
"coshf", "erff", "exp10f", "exp2f", "expf", "expm1f", "fabsf", "floorf", "j0f", "j1f",
|
||||
"lgammaf", "log10f", "log1pf", "log2f", "logf", "rintf", "roundf", "sinf", "sinhf",
|
||||
"sqrtf", "tanf", "tanhf", "tgammaf", "truncf",
|
||||
],
|
||||
),
|
||||
(
|
||||
// `(f64) -> f64`
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&[
|
||||
"acos", "acosh", "asin", "asinh", "atan", "atanh", "cbrt", "ceil", "cos", "cosh",
|
||||
"erf", "exp10", "exp2", "exp", "expm1", "fabs", "floor", "j0", "j1", "lgamma", "log10",
|
||||
"log1p", "log2", "log", "rint", "round", "sin", "sinh", "sqrt", "tan", "tanh",
|
||||
"tgamma", "trunc",
|
||||
],
|
||||
),
|
||||
(
|
||||
// `(f32, f32) -> f32`
|
||||
Signature { args: &[Ty::F32, Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&[
|
||||
"atan2f",
|
||||
"copysignf",
|
||||
"fdimf",
|
||||
"fmaxf",
|
||||
"fminf",
|
||||
"fmodf",
|
||||
"hypotf",
|
||||
"nextafterf",
|
||||
"powf",
|
||||
"remainderf",
|
||||
],
|
||||
),
|
||||
(
|
||||
// `(f64, f64) -> f64`
|
||||
Signature { args: &[Ty::F64, Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&[
|
||||
"atan2",
|
||||
"copysign",
|
||||
"fdim",
|
||||
"fmax",
|
||||
"fmin",
|
||||
"fmod",
|
||||
"hypot",
|
||||
"nextafter",
|
||||
"pow",
|
||||
"remainder",
|
||||
],
|
||||
),
|
||||
(
|
||||
// `(f32, f32, f32) -> f32`
|
||||
Signature { args: &[Ty::F32, Ty::F32, Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&["fmaf"],
|
||||
),
|
||||
(
|
||||
// `(f64, f64, f64) -> f64`
|
||||
Signature { args: &[Ty::F64, Ty::F64, Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&["fma"],
|
||||
),
|
||||
(
|
||||
// `(f32) -> i32`
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::I32] },
|
||||
None,
|
||||
&["ilogbf"],
|
||||
),
|
||||
(
|
||||
// `(f64) -> i32`
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::I32] },
|
||||
None,
|
||||
&["ilogb"],
|
||||
),
|
||||
(
|
||||
// `(i32, f32) -> f32`
|
||||
Signature { args: &[Ty::I32, Ty::F32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&["jnf"],
|
||||
),
|
||||
(
|
||||
// `(i32, f64) -> f64`
|
||||
Signature { args: &[Ty::I32, Ty::F64], returns: &[Ty::F64] },
|
||||
None,
|
||||
&["jn"],
|
||||
),
|
||||
(
|
||||
// `(f32, i32) -> f32`
|
||||
Signature { args: &[Ty::F32, Ty::I32], returns: &[Ty::F32] },
|
||||
None,
|
||||
&["scalbnf", "ldexpf"],
|
||||
),
|
||||
(
|
||||
// `(f64, i64) -> f64`
|
||||
Signature { args: &[Ty::F64, Ty::I32], returns: &[Ty::F64] },
|
||||
None,
|
||||
&["scalbn", "ldexp"],
|
||||
),
|
||||
(
|
||||
// `(f32, &mut f32) -> f32` as `(f32) -> (f32, f32)`
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32, Ty::F32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::MutF32], returns: &[Ty::F32] }),
|
||||
&["modff"],
|
||||
),
|
||||
(
|
||||
// `(f64, &mut f64) -> f64` as `(f64) -> (f64, f64)`
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64, Ty::F64] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::MutF64], returns: &[Ty::F64] }),
|
||||
&["modf"],
|
||||
),
|
||||
(
|
||||
// `(f32, &mut c_int) -> f32` as `(f32) -> (f32, i32)`
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::MutCInt], returns: &[Ty::F32] }),
|
||||
&["frexpf", "lgammaf_r"],
|
||||
),
|
||||
(
|
||||
// `(f64, &mut c_int) -> f64` as `(f64) -> (f64, i32)`
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::MutCInt], returns: &[Ty::F64] }),
|
||||
&["frexp", "lgamma_r"],
|
||||
),
|
||||
(
|
||||
// `(f32, f32, &mut c_int) -> f32` as `(f32, f32) -> (f32, i32)`
|
||||
Signature { args: &[Ty::F32, Ty::F32], returns: &[Ty::F32, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::F32, Ty::MutCInt], returns: &[Ty::F32] }),
|
||||
&["remquof"],
|
||||
),
|
||||
(
|
||||
// `(f64, f64, &mut c_int) -> f64` as `(f64, f64) -> (f64, i32)`
|
||||
Signature { args: &[Ty::F64, Ty::F64], returns: &[Ty::F64, Ty::I32] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::F64, Ty::MutCInt], returns: &[Ty::F64] }),
|
||||
&["remquo"],
|
||||
),
|
||||
(
|
||||
// `(f32, &mut f32, &mut f32)` as `(f32) -> (f32, f32)`
|
||||
Signature { args: &[Ty::F32], returns: &[Ty::F32, Ty::F32] },
|
||||
Some(Signature { args: &[Ty::F32, Ty::MutF32, Ty::MutF32], returns: &[] }),
|
||||
&["sincosf"],
|
||||
),
|
||||
(
|
||||
// `(f64, &mut f64, &mut f64)` as `(f64) -> (f64, f64)`
|
||||
Signature { args: &[Ty::F64], returns: &[Ty::F64, Ty::F64] },
|
||||
Some(Signature { args: &[Ty::F64, Ty::MutF64, Ty::MutF64], returns: &[] }),
|
||||
&["sincos"],
|
||||
),
|
||||
];
|
||||
|
||||
/// A type used in a function signature.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Ty {
|
||||
F16,
|
||||
F32,
|
||||
F64,
|
||||
F128,
|
||||
I32,
|
||||
CInt,
|
||||
MutF16,
|
||||
MutF32,
|
||||
MutF64,
|
||||
MutF128,
|
||||
MutI32,
|
||||
MutCInt,
|
||||
}
|
||||
|
||||
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! { &mut f16 },
|
||||
Ty::MutF32 => quote! { &mut f32 },
|
||||
Ty::MutF64 => quote! { &mut f64 },
|
||||
Ty::MutF128 => quote! { &mut f128 },
|
||||
Ty::MutI32 => quote! { &mut i32 },
|
||||
Ty::MutCInt => quote! { &mut core::ffi::c_int },
|
||||
};
|
||||
|
||||
tokens.extend(ts);
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of e.g. `(f32, f32) -> f32`
|
||||
#[derive(Debug, Clone)]
|
||||
struct Signature {
|
||||
args: &'static [Ty],
|
||||
returns: &'static [Ty],
|
||||
}
|
||||
|
||||
/// Combined information about a function implementation.
|
||||
#[derive(Debug, Clone)]
|
||||
struct FunctionInfo {
|
||||
name: &'static str,
|
||||
/// Function signature for C implementations
|
||||
c_sig: Signature,
|
||||
/// Function signature for Rust implementations
|
||||
rust_sig: Signature,
|
||||
}
|
||||
|
||||
/// A flat representation of `ALL_FUNCTIONS`.
|
||||
static ALL_FUNCTIONS_FLAT: LazyLock<Vec<FunctionInfo>> = LazyLock::new(|| {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for (rust_sig, c_sig, names) in ALL_FUNCTIONS {
|
||||
for name in *names {
|
||||
let api = FunctionInfo {
|
||||
name,
|
||||
rust_sig: rust_sig.clone(),
|
||||
c_sig: c_sig.clone().unwrap_or_else(|| rust_sig.clone()),
|
||||
};
|
||||
ret.push(api);
|
||||
}
|
||||
}
|
||||
|
||||
ret.sort_by_key(|item| item.name);
|
||||
ret
|
||||
});
|
||||
|
||||
/// 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,
|
||||
/// // 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,
|
||||
/// // Attributes for the current function, if any
|
||||
/// attrs: [$($meta:meta)*]
|
||||
/// // Extra tokens passed directly (if any)
|
||||
/// extra: [$extra:ident],
|
||||
/// // Extra function-tokens passed directly (if any)
|
||||
/// fn_extra: $fn_extra:expr,
|
||||
/// ) => { };
|
||||
/// }
|
||||
///
|
||||
/// libm_macros::for_each_function! {
|
||||
/// // The macro to invoke as a callback
|
||||
/// callback: callback_macro,
|
||||
/// // Functions to skip, i.e. `callback` shouldn't be called at all for these.
|
||||
/// //
|
||||
/// // This is an optional field.
|
||||
/// 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.
|
||||
/// //
|
||||
/// // This is an optional field.
|
||||
/// 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.
|
||||
/// //
|
||||
/// // This is an optional field.
|
||||
/// 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(),
|
||||
/// _ => |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(|s_in| validate(&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: &StructuredInput) -> syn::Result<Vec<&'static FunctionInfo>> {
|
||||
// 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_FUNCTIONS_FLAT.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(),
|
||||
format!("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_FUNCTIONS_FLAT.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;
|
||||
}
|
||||
|
||||
// Run everything else
|
||||
fn_list.push(func);
|
||||
}
|
||||
|
||||
if let Some(map) = &input.fn_extra {
|
||||
if !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: &[&FunctionInfo]) -> 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 meta_field = match &input.attributes {
|
||||
Some(attrs) => {
|
||||
let meta = attrs
|
||||
.iter()
|
||||
.filter(|map| map.names.contains(&fn_name))
|
||||
.flat_map(|map| &map.meta);
|
||||
quote! { attrs: [ #( #meta )* ] }
|
||||
}
|
||||
None => pm2::TokenStream::new(),
|
||||
};
|
||||
|
||||
// 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 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 new = quote! {
|
||||
#callback! {
|
||||
fn_name: #fn_name,
|
||||
CFn: fn( #(#c_args),* ,) -> ( #(#c_ret),* ),
|
||||
CArgs: ( #(#c_args),* ,),
|
||||
CRet: ( #(#c_ret),* ),
|
||||
RustFn: fn( #(#rust_args),* ,) -> ( #(#rust_ret),* ),
|
||||
RustArgs: ( #(#rust_args),* ,),
|
||||
RustRet: ( #(#rust_ret),* ),
|
||||
#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 {
|
||||
// Keep this in sync with `libm_test::canonical_name`
|
||||
let known_mappings = &[
|
||||
("erff", "erf"),
|
||||
("erf", "erf"),
|
||||
("lgammaf_r", "lgamma_r"),
|
||||
("modff", "modf"),
|
||||
("modf", "modf"),
|
||||
];
|
||||
|
||||
let norm_name = 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),
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
236
library/compiler-builtins/libm/crates/libm-macros/src/parse.rs
Normal file
236
library/compiler-builtins/libm/crates/libm-macros/src/parse.rs
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
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::Comma;
|
||||
use syn::{Arm, Attribute, Expr, ExprMatch, Ident, 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,
|
||||
/// Skip these functions
|
||||
pub skip: Vec<Ident>,
|
||||
/// 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 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 skip_expr = expect_field(&mut map, "skip").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 skip = match skip_expr {
|
||||
Some(expr) => Parser::parse2(parse_ident_array, expr.into_token_stream())?,
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
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)?,
|
||||
skip,
|
||||
only,
|
||||
only_span,
|
||||
attributes,
|
||||
extra,
|
||||
fn_extra,
|
||||
fn_extra_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())
|
||||
}
|
||||
|
||||
/// 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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// `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,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
attrs: [$($meta:meta)*]
|
||||
extra: [$($extra_tt:tt)*],
|
||||
fn_extra: $fn_extra:expr,
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
mod $fn_name {
|
||||
#[allow(unused)]
|
||||
type CFnTy = $CFn;
|
||||
// type CArgsTy<'_> = $CArgs;
|
||||
// type CRetTy<'_> = $CRet;
|
||||
#[allow(unused)]
|
||||
type RustFnTy = $RustFn;
|
||||
#[allow(unused)]
|
||||
type RustArgsTy = $RustArgs;
|
||||
#[allow(unused)]
|
||||
type RustRetTy = $RustRet;
|
||||
#[allow(unused)]
|
||||
const A: &[&str] = &[$($extra_tt)*];
|
||||
#[allow(unused)]
|
||||
fn foo(a: f32) -> f32 {
|
||||
$fn_extra(a)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod test_basic {
|
||||
libm_macros::for_each_function! {
|
||||
callback: basic,
|
||||
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,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
) => {
|
||||
mod $fn_name {
|
||||
#[allow(unused)]
|
||||
type CFnTy = $CFn;
|
||||
// type CArgsTy<'_> = $CArgs;
|
||||
// type CRetTy<'_> = $CRet;
|
||||
#[allow(unused)]
|
||||
type RustFnTy = $RustFn;
|
||||
#[allow(unused)]
|
||||
type RustArgsTy = $RustArgs;
|
||||
#[allow(unused)]
|
||||
type RustRetTy = $RustRet;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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],
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,21 @@ default = []
|
|||
# musl libc.
|
||||
test-musl-serialized = ["rand"]
|
||||
|
||||
# Build our own musl for testing and benchmarks
|
||||
build-musl = ["dep:musl-math-sys"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.90"
|
||||
libm = { path = "../.." }
|
||||
libm-macros = { path = "../libm-macros" }
|
||||
musl-math-sys = { path = "../musl-math-sys", optional = true }
|
||||
paste = "1.0.15"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
# Enable randomness on WASM
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
[build-dependencies]
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,106 @@
|
|||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::from_env();
|
||||
|
||||
emit_optimization_cfg(&cfg);
|
||||
emit_cfg_shorthands(&cfg);
|
||||
list_all_tests(&cfg);
|
||||
|
||||
#[cfg(feature = "test-musl-serialized")]
|
||||
musl_reference_tests::generate();
|
||||
musl_serialized_tests::generate();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct Config {
|
||||
manifest_dir: PathBuf,
|
||||
out_dir: PathBuf,
|
||||
opt_level: u8,
|
||||
target_arch: String,
|
||||
target_env: String,
|
||||
target_family: Option<String>,
|
||||
target_os: String,
|
||||
target_string: String,
|
||||
target_vendor: String,
|
||||
target_features: Vec<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn from_env() -> Self {
|
||||
let target_features = env::var("CARGO_CFG_TARGET_FEATURE")
|
||||
.map(|feats| feats.split(',').map(ToOwned::to_owned).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
Self {
|
||||
manifest_dir: PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()),
|
||||
out_dir: PathBuf::from(env::var("OUT_DIR").unwrap()),
|
||||
opt_level: env::var("OPT_LEVEL").unwrap().parse().unwrap(),
|
||||
target_arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
|
||||
target_env: env::var("CARGO_CFG_TARGET_ENV").unwrap(),
|
||||
target_family: env::var("CARGO_CFG_TARGET_FAMILY").ok(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Some tests are extremely slow. Emit a config option based on optimization level.
|
||||
fn emit_optimization_cfg(cfg: &Config) {
|
||||
println!("cargo::rustc-check-cfg=cfg(optimizations_enabled)");
|
||||
|
||||
if cfg.opt_level >= 2 {
|
||||
println!("cargo::rustc-cfg=optimizations_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide an alias for common longer config combinations.
|
||||
fn emit_cfg_shorthands(cfg: &Config) {
|
||||
println!("cargo::rustc-check-cfg=cfg(x86_no_sse)");
|
||||
if cfg.target_arch == "x86" && !cfg.target_features.iter().any(|f| f == "sse") {
|
||||
// Shorthand to detect i586 targets
|
||||
println!("cargo::rustc-cfg=x86_no_sse");
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a list of all source files in an array. This can be used for making sure that
|
||||
/// all functions are tested or otherwise covered in some way.
|
||||
// FIXME: it would probably be better to use rustdoc JSON output to get public functions.
|
||||
fn list_all_tests(cfg: &Config) {
|
||||
let math_src = cfg.manifest_dir.join("../../src/math");
|
||||
|
||||
let mut files = fs::read_dir(math_src)
|
||||
.unwrap()
|
||||
.map(|f| f.unwrap().path())
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|f| f.file_stem().unwrap().to_str().unwrap().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
files.sort();
|
||||
|
||||
let mut s = "pub const ALL_FUNCTIONS: &[&str] = &[".to_owned();
|
||||
for f in files {
|
||||
if f == "mod" {
|
||||
// skip mod.rs
|
||||
continue;
|
||||
}
|
||||
write!(s, "\"{f}\",").unwrap();
|
||||
}
|
||||
write!(s, "];").unwrap();
|
||||
|
||||
let outfile = cfg.out_dir.join("all_files.rs");
|
||||
fs::write(outfile, s).unwrap();
|
||||
}
|
||||
|
||||
/// At build time, generate the output of what the corresponding `*musl` target does with a range
|
||||
/// of inputs.
|
||||
///
|
||||
/// Serialize that target's output, run the same thing with our symbols, then load and compare
|
||||
/// the resulting values.
|
||||
#[cfg(feature = "test-musl-serialized")]
|
||||
mod musl_reference_tests {
|
||||
mod musl_serialized_tests {
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env, fs};
|
||||
|
|
|
|||
72
library/compiler-builtins/libm/crates/libm-test/src/gen.rs
Normal file
72
library/compiler-builtins/libm/crates/libm-test/src/gen.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
//! Different generators that can create random or systematic bit patterns.
|
||||
|
||||
use crate::GenerateInput;
|
||||
pub mod random;
|
||||
|
||||
/// Helper type to turn any reusable input into a generator.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CachedInput {
|
||||
pub inputs_f32: Vec<(f32, f32, f32)>,
|
||||
pub inputs_f64: Vec<(f64, f64, f64)>,
|
||||
pub inputs_i32: Vec<(i32, i32, i32)>,
|
||||
}
|
||||
|
||||
impl GenerateInput<(f32,)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f32,)> {
|
||||
self.inputs_f32.iter().map(|f| (f.0,))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(f32, f32)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f32, f32)> {
|
||||
self.inputs_f32.iter().map(|f| (f.0, f.1))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(i32, f32)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (i32, f32)> {
|
||||
self.inputs_i32.iter().zip(self.inputs_f32.iter()).map(|(i, f)| (i.0, f.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(f32, i32)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f32, i32)> {
|
||||
GenerateInput::<(i32, f32)>::get_cases(self).map(|(i, f)| (f, i))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(f32, f32, f32)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f32, f32, f32)> {
|
||||
self.inputs_f32.iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(f64,)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f64,)> {
|
||||
self.inputs_f64.iter().map(|f| (f.0,))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(f64, f64)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f64, f64)> {
|
||||
self.inputs_f64.iter().map(|f| (f.0, f.1))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(i32, f64)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (i32, f64)> {
|
||||
self.inputs_i32.iter().zip(self.inputs_f64.iter()).map(|(i, f)| (i.0, f.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(f64, i32)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f64, i32)> {
|
||||
GenerateInput::<(i32, f64)>::get_cases(self).map(|(i, f)| (f, i))
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateInput<(f64, f64, f64)> for CachedInput {
|
||||
fn get_cases(&self) -> impl Iterator<Item = (f64, f64, f64)> {
|
||||
self.inputs_f64.iter().copied()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
//! A simple generator that produces deterministic random input, caching to use the same
|
||||
//! inputs for all functions.
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
use super::CachedInput;
|
||||
use crate::GenerateInput;
|
||||
|
||||
const SEED: [u8; 32] = *b"3.141592653589793238462643383279";
|
||||
|
||||
/// Number of tests to run.
|
||||
const NTESTS: usize = {
|
||||
let ntests = if cfg!(optimizations_enabled) {
|
||||
if cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64") {
|
||||
5_000_000
|
||||
} else if !cfg!(target_pointer_width = "64")
|
||||
|| cfg!(all(target_arch = "x86_64", target_vendor = "apple"))
|
||||
|| option_env!("EMULATED").is_some()
|
||||
&& cfg!(any(target_arch = "aarch64", target_arch = "powerpc64"))
|
||||
{
|
||||
// Tests are pretty slow on:
|
||||
// - Non-64-bit targets
|
||||
// - Emulated ppc
|
||||
// - Emulated aarch64
|
||||
// - x86 MacOS
|
||||
// So reduce the number of iterations
|
||||
100_000
|
||||
} else {
|
||||
// Most everything else gets tested in docker and works okay, but we still
|
||||
// don't need 20 minutes of tests.
|
||||
1_000_000
|
||||
}
|
||||
} else {
|
||||
800
|
||||
};
|
||||
|
||||
ntests
|
||||
};
|
||||
|
||||
/// Tested inputs.
|
||||
static TEST_CASES: LazyLock<CachedInput> = LazyLock::new(|| make_test_cases(NTESTS));
|
||||
|
||||
/// The first argument to `jn` and `jnf` is the number of iterations. Make this a reasonable
|
||||
/// value so tests don't run forever.
|
||||
static TEST_CASES_JN: LazyLock<CachedInput> = LazyLock::new(|| {
|
||||
// Start with regular test cases
|
||||
let mut cases = (&*TEST_CASES).clone();
|
||||
|
||||
// These functions are extremely slow, limit them
|
||||
cases.inputs_i32.truncate((NTESTS / 1000).max(80));
|
||||
cases.inputs_f32.truncate((NTESTS / 1000).max(80));
|
||||
cases.inputs_f64.truncate((NTESTS / 1000).max(80));
|
||||
|
||||
// It is easy to overflow the stack with these in debug mode
|
||||
let max_iterations = if cfg!(optimizations_enabled) && cfg!(target_pointer_width = "64") {
|
||||
0xffff
|
||||
} else if cfg!(windows) {
|
||||
0x00ff
|
||||
} else {
|
||||
0x0fff
|
||||
};
|
||||
|
||||
let mut rng = ChaCha8Rng::from_seed(SEED);
|
||||
|
||||
for case in cases.inputs_i32.iter_mut() {
|
||||
case.0 = rng.gen_range(3..=max_iterations);
|
||||
}
|
||||
|
||||
cases
|
||||
});
|
||||
|
||||
fn make_test_cases(ntests: usize) -> CachedInput {
|
||||
let mut rng = ChaCha8Rng::from_seed(SEED);
|
||||
|
||||
// make sure we include some basic cases
|
||||
let mut inputs_i32 = vec![(0, 0, 0), (1, 1, 1), (-1, -1, -1)];
|
||||
let mut inputs_f32 = vec![
|
||||
(0.0, 0.0, 0.0),
|
||||
(f32::EPSILON, f32::EPSILON, f32::EPSILON),
|
||||
(f32::INFINITY, f32::INFINITY, f32::INFINITY),
|
||||
(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY),
|
||||
(f32::MAX, f32::MAX, f32::MAX),
|
||||
(f32::MIN, f32::MIN, f32::MIN),
|
||||
(f32::MIN_POSITIVE, f32::MIN_POSITIVE, f32::MIN_POSITIVE),
|
||||
(f32::NAN, f32::NAN, f32::NAN),
|
||||
];
|
||||
let mut inputs_f64 = vec![
|
||||
(0.0, 0.0, 0.0),
|
||||
(f64::EPSILON, f64::EPSILON, f64::EPSILON),
|
||||
(f64::INFINITY, f64::INFINITY, f64::INFINITY),
|
||||
(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
|
||||
(f64::MAX, f64::MAX, f64::MAX),
|
||||
(f64::MIN, f64::MIN, f64::MIN),
|
||||
(f64::MIN_POSITIVE, f64::MIN_POSITIVE, f64::MIN_POSITIVE),
|
||||
(f64::NAN, f64::NAN, f64::NAN),
|
||||
];
|
||||
|
||||
inputs_i32.extend((0..(ntests - inputs_i32.len())).map(|_| rng.gen::<(i32, i32, i32)>()));
|
||||
|
||||
// Generate integers to get a full range of bitpatterns, then convert back to
|
||||
// floats.
|
||||
inputs_f32.extend((0..(ntests - inputs_f32.len())).map(|_| {
|
||||
let ints = rng.gen::<(u32, u32, u32)>();
|
||||
(f32::from_bits(ints.0), f32::from_bits(ints.1), f32::from_bits(ints.2))
|
||||
}));
|
||||
inputs_f64.extend((0..(ntests - inputs_f64.len())).map(|_| {
|
||||
let ints = rng.gen::<(u64, u64, u64)>();
|
||||
(f64::from_bits(ints.0), f64::from_bits(ints.1), f64::from_bits(ints.2))
|
||||
}));
|
||||
|
||||
CachedInput { inputs_f32, inputs_f64, inputs_i32 }
|
||||
}
|
||||
|
||||
/// Create a test case iterator.
|
||||
pub fn get_test_cases<RustArgs>(fname: &str) -> impl Iterator<Item = RustArgs>
|
||||
where
|
||||
CachedInput: GenerateInput<RustArgs>,
|
||||
{
|
||||
let inputs = if fname == "jn" || fname == "jnf" { &TEST_CASES_JN } else { &TEST_CASES };
|
||||
|
||||
CachedInput::get_cases(inputs)
|
||||
}
|
||||
|
|
@ -1 +1,64 @@
|
|||
pub mod gen;
|
||||
mod num_traits;
|
||||
mod special_case;
|
||||
mod test_traits;
|
||||
|
||||
pub use num_traits::{Float, Hex, Int};
|
||||
pub use special_case::{MaybeOverride, SpecialCase};
|
||||
pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, TupleCall};
|
||||
|
||||
/// Result type for tests is usually from `anyhow`. Most times there is no success value to
|
||||
/// propagate.
|
||||
pub type TestResult<T = (), E = anyhow::Error> = Result<T, E>;
|
||||
|
||||
// List of all files present in libm's source
|
||||
include!(concat!(env!("OUT_DIR"), "/all_files.rs"));
|
||||
|
||||
/// ULP allowed to differ from musl (note that musl itself may not be accurate).
|
||||
const MUSL_DEFAULT_ULP: u32 = 2;
|
||||
|
||||
/// Certain functions have different allowed ULP (consider these xfail).
|
||||
///
|
||||
/// Note that these results were obtained using 400,000,000 rounds of random inputs, which
|
||||
/// is not a value used by default.
|
||||
pub fn musl_allowed_ulp(name: &str) -> u32 {
|
||||
match name {
|
||||
#[cfg(x86_no_sse)]
|
||||
"asinh" | "asinhf" => 6,
|
||||
"lgamma" | "lgamma_r" | "lgammaf" | "lgammaf_r" => 400,
|
||||
"tanh" | "tanhf" => 4,
|
||||
"tgamma" => 20,
|
||||
"j0" | "j0f" | "j1" | "j1f" => {
|
||||
// Results seem very target-dependent
|
||||
if cfg!(target_arch = "x86_64") { 4000 } else { 800_000 }
|
||||
}
|
||||
"jn" | "jnf" => 1000,
|
||||
"sincosf" => 500,
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
"exp10" => 4,
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
"exp10f" => 4,
|
||||
_ => MUSL_DEFAULT_ULP,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
pub fn canonical_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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::{MaybeOverride, SpecialCase, TestResult};
|
||||
|
||||
/// Common types and methods for floating point numbers.
|
||||
pub trait Float: Copy + fmt::Display + fmt::Debug + PartialEq<Self> {
|
||||
type Int: Int<OtherSign = Self::SignedInt, Unsigned = Self::Int>;
|
||||
type SignedInt: Int + Int<OtherSign = Self::Int, Unsigned = Self::Int>;
|
||||
|
||||
const ZERO: Self;
|
||||
const ONE: Self;
|
||||
|
||||
/// The bitwidth of the float type
|
||||
const BITS: u32;
|
||||
|
||||
/// The bitwidth of the significand
|
||||
const SIGNIFICAND_BITS: u32;
|
||||
|
||||
/// The bitwidth of the exponent
|
||||
const EXPONENT_BITS: u32 = Self::BITS - Self::SIGNIFICAND_BITS - 1;
|
||||
|
||||
fn is_nan(self) -> bool;
|
||||
fn is_infinite(self) -> bool;
|
||||
fn to_bits(self) -> Self::Int;
|
||||
fn from_bits(bits: Self::Int) -> Self;
|
||||
fn signum(self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_float {
|
||||
($($fty:ty, $ui:ty, $si:ty, $significand_bits:expr;)+) => {
|
||||
$(
|
||||
impl Float for $fty {
|
||||
type Int = $ui;
|
||||
type SignedInt = $si;
|
||||
|
||||
const ZERO: Self = 0.0;
|
||||
const ONE: Self = 1.0;
|
||||
|
||||
const BITS: u32 = <$ui>::BITS;
|
||||
const SIGNIFICAND_BITS: u32 = $significand_bits;
|
||||
|
||||
fn is_nan(self) -> bool {
|
||||
self.is_nan()
|
||||
}
|
||||
fn is_infinite(self) -> bool {
|
||||
self.is_infinite()
|
||||
}
|
||||
fn to_bits(self) -> Self::Int {
|
||||
self.to_bits()
|
||||
}
|
||||
fn from_bits(bits: Self::Int) -> Self {
|
||||
Self::from_bits(bits)
|
||||
}
|
||||
fn signum(self) -> Self {
|
||||
self.signum()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hex for $fty {
|
||||
fn hex(self) -> String {
|
||||
self.to_bits().hex()
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
impl_float!(
|
||||
f32, u32, i32, 23;
|
||||
f64, u64, i64, 52;
|
||||
);
|
||||
|
||||
/// Common types and methods for integers.
|
||||
pub trait Int: Copy + fmt::Display + fmt::Debug + PartialEq<Self> {
|
||||
type OtherSign: Int;
|
||||
type Unsigned: Int;
|
||||
const BITS: u32;
|
||||
const SIGNED: bool;
|
||||
|
||||
fn signed(self) -> <Self::Unsigned as Int>::OtherSign;
|
||||
fn unsigned(self) -> Self::Unsigned;
|
||||
fn checked_sub(self, other: Self) -> Option<Self>;
|
||||
fn abs(self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_int {
|
||||
($($ui:ty, $si:ty ;)+) => {
|
||||
$(
|
||||
impl Int for $ui {
|
||||
type OtherSign = $si;
|
||||
type Unsigned = Self;
|
||||
const BITS: u32 = <$ui>::BITS;
|
||||
const SIGNED: bool = false;
|
||||
fn signed(self) -> Self::OtherSign {
|
||||
self as $si
|
||||
}
|
||||
fn unsigned(self) -> Self {
|
||||
self
|
||||
}
|
||||
fn checked_sub(self, other: Self) -> Option<Self> {
|
||||
self.checked_sub(other)
|
||||
}
|
||||
fn abs(self) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Int for $si {
|
||||
type OtherSign = $ui;
|
||||
type Unsigned = $ui;
|
||||
const BITS: u32 = <$ui>::BITS;
|
||||
const SIGNED: bool = true;
|
||||
fn signed(self) -> Self {
|
||||
self
|
||||
}
|
||||
fn unsigned(self) -> $ui {
|
||||
self as $ui
|
||||
}
|
||||
fn checked_sub(self, other: Self) -> Option<Self> {
|
||||
self.checked_sub(other)
|
||||
}
|
||||
fn abs(self) -> Self {
|
||||
self.abs()
|
||||
}
|
||||
}
|
||||
|
||||
impl_int!(@for_both $si);
|
||||
impl_int!(@for_both $ui);
|
||||
|
||||
)+
|
||||
};
|
||||
|
||||
(@for_both $ty:ty) => {
|
||||
impl Hex for $ty {
|
||||
fn hex(self) -> String {
|
||||
format!("{self:#0width$x}", width = ((Self::BITS / 4) + 2) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Input> $crate::CheckOutput<Input> for $ty
|
||||
where
|
||||
Input: Hex + fmt::Debug,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
fn validate<'a>(
|
||||
self,
|
||||
expected: Self,
|
||||
input: Input,
|
||||
ctx: &$crate::CheckCtx,
|
||||
) -> TestResult {
|
||||
if let Some(res) = SpecialCase::check_int(input, self, expected, ctx) {
|
||||
return res;
|
||||
}
|
||||
|
||||
anyhow::ensure!(
|
||||
self == expected,
|
||||
"\
|
||||
\n input: {input:?} {ibits}\
|
||||
\n expected: {expected:<22?} {expbits}\
|
||||
\n actual: {self:<22?} {actbits}\
|
||||
",
|
||||
actbits = self.hex(),
|
||||
expbits = expected.hex(),
|
||||
ibits = input.hex(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_int!(
|
||||
u32, i32;
|
||||
u64, i64;
|
||||
);
|
||||
|
||||
/// A helper trait to print something as hex with the correct number of nibbles, e.g. a `u32`
|
||||
/// will always print with `0x` followed by 8 digits.
|
||||
///
|
||||
/// This is only used for printing errors so allocating is okay.
|
||||
pub trait Hex: Copy {
|
||||
fn hex(self) -> String;
|
||||
}
|
||||
|
||||
impl<T1> Hex for (T1,)
|
||||
where
|
||||
T1: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({},)", self.0.hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2> Hex for (T1, T2)
|
||||
where
|
||||
T1: Hex,
|
||||
T2: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({}, {})", self.0.hex(), self.1.hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3> Hex for (T1, T2, T3)
|
||||
where
|
||||
T1: Hex,
|
||||
T2: Hex,
|
||||
T3: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({}, {}, {})", self.0.hex(), self.1.hex(), self.2.hex())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
//! Configuration for skipping or changing the result for individual test cases (inputs) rather
|
||||
//! than ignoring entire tests.
|
||||
|
||||
use core::f32;
|
||||
|
||||
use crate::{CheckBasis, CheckCtx, Float, Int, TestResult};
|
||||
|
||||
/// Type implementing [`IgnoreCase`].
|
||||
pub struct SpecialCase;
|
||||
|
||||
/// Don't run further validation on this test case.
|
||||
const SKIP: Option<TestResult> = Some(Ok(()));
|
||||
|
||||
/// Return this to skip checks on a test that currently fails but shouldn't. Looks
|
||||
/// the same as skip, but we keep them separate to better indicate purpose.
|
||||
const XFAIL: Option<TestResult> = Some(Ok(()));
|
||||
|
||||
/// Allow overriding the outputs of specific test cases.
|
||||
///
|
||||
/// There are some cases where we want to xfail specific cases or handle certain inputs
|
||||
/// differently than the rest of calls to `validate`. This provides a hook to do that.
|
||||
///
|
||||
/// If `None` is returned, checks will proceed as usual. If `Some(result)` is returned, checks
|
||||
/// are skipped and the provided result is returned instead.
|
||||
///
|
||||
/// This gets implemented once per input type, then the functions provide further filtering
|
||||
/// based on function name and values.
|
||||
///
|
||||
/// `ulp` can also be set to adjust the ULP for that specific test, even if `None` is still
|
||||
/// returned.
|
||||
pub trait MaybeOverride<Input> {
|
||||
fn check_float<F: Float>(
|
||||
_input: Input,
|
||||
_actual: F,
|
||||
_expected: F,
|
||||
_ulp: &mut u32,
|
||||
_ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
None
|
||||
}
|
||||
|
||||
fn check_int<I: Int>(
|
||||
_input: Input,
|
||||
_actual: I,
|
||||
_expected: I,
|
||||
_ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeOverride<(f32,)> for SpecialCase {
|
||||
fn check_float<F: Float>(
|
||||
input: (f32,),
|
||||
actual: F,
|
||||
expected: F,
|
||||
_ulp: &mut u32,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
if ctx.basis == CheckBasis::Musl {
|
||||
if ctx.fname == "acoshf" && input.0 < -1.0 {
|
||||
// acoshf is undefined for x <= 1.0, but we return a random result at lower
|
||||
// values.
|
||||
return XFAIL;
|
||||
}
|
||||
|
||||
if ctx.fname == "sincosf" {
|
||||
let factor_frac_pi_2 = input.0.abs() / f32::consts::FRAC_PI_2;
|
||||
if (factor_frac_pi_2 - factor_frac_pi_2.round()).abs() < 1e-2 {
|
||||
// we have a bad approximation near multiples of pi/2
|
||||
return XFAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.fname == "expm1f" && input.0 > 80.0 && actual.is_infinite() {
|
||||
// we return infinity but the number is representable
|
||||
return XFAIL;
|
||||
}
|
||||
|
||||
if ctx.fname == "sinhf" && input.0.abs() > 80.0 && actual.is_nan() {
|
||||
// we return some NaN that should be real values or infinite
|
||||
// doesn't seem to happen on x86
|
||||
return XFAIL;
|
||||
}
|
||||
|
||||
if ctx.fname == "lgammaf" || ctx.fname == "lgammaf_r" && input.0 < 0.0 {
|
||||
// loggamma should not be defined for x < 0, yet we both return results
|
||||
return XFAIL;
|
||||
}
|
||||
}
|
||||
|
||||
maybe_check_nan_bits(actual, expected, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeOverride<(f64,)> for SpecialCase {
|
||||
fn check_float<F: Float>(
|
||||
input: (f64,),
|
||||
actual: F,
|
||||
expected: F,
|
||||
_ulp: &mut u32,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
if ctx.basis == CheckBasis::Musl {
|
||||
if cfg!(target_arch = "x86") && ctx.fname == "acosh" && input.0 < 1.0 {
|
||||
// The function is undefined, both implementations return random results
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
if cfg!(x86_no_sse)
|
||||
&& ctx.fname == "ceil"
|
||||
&& input.0 < 0.0
|
||||
&& input.0 > -1.0
|
||||
&& expected == F::ZERO
|
||||
&& actual == F::ZERO
|
||||
{
|
||||
// musl returns -0.0, we return +0.0
|
||||
return XFAIL;
|
||||
}
|
||||
|
||||
if ctx.fname == "lgamma" || ctx.fname == "lgamma_r" && input.0 < 0.0 {
|
||||
// loggamma should not be defined for x < 0, yet we both return results
|
||||
return XFAIL;
|
||||
}
|
||||
}
|
||||
|
||||
maybe_check_nan_bits(actual, expected, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check NaN bits if the function requires it
|
||||
fn maybe_check_nan_bits<F: Float>(actual: F, expected: F, ctx: &CheckCtx) -> Option<TestResult> {
|
||||
if !(ctx.canonical_name == "fabs" || ctx.canonical_name == "copysign") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// LLVM currently uses x87 instructions which quieten signalling NaNs to handle the i686
|
||||
// `extern "C"` `f32`/`f64` return ABI.
|
||||
// LLVM issue <https://github.com/llvm/llvm-project/issues/66803>
|
||||
// Rust issue <https://github.com/rust-lang/rust/issues/115567>
|
||||
if cfg!(target_arch = "x86") && ctx.basis == CheckBasis::Musl {
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
// abs and copysign require signaling NaNs to be propagated, so verify bit equality.
|
||||
if actual.to_bits() == expected.to_bits() {
|
||||
return SKIP;
|
||||
} else {
|
||||
Some(Err(anyhow::anyhow!("NaNs have different bitpatterns")))
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeOverride<(f32, f32)> for SpecialCase {
|
||||
fn check_float<F: Float>(
|
||||
input: (f32, f32),
|
||||
_actual: F,
|
||||
expected: F,
|
||||
_ulp: &mut u32,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
maybe_skip_min_max_nan(input, expected, ctx)
|
||||
}
|
||||
}
|
||||
impl MaybeOverride<(f64, f64)> for SpecialCase {
|
||||
fn check_float<F: Float>(
|
||||
input: (f64, f64),
|
||||
_actual: F,
|
||||
expected: F,
|
||||
_ulp: &mut u32,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
maybe_skip_min_max_nan(input, expected, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Musl propagates NaNs if one is provided as the input, but we return the other input.
|
||||
// F1 and F2 are always the same type, this is just to please generics
|
||||
fn maybe_skip_min_max_nan<F1: Float, F2: Float>(
|
||||
input: (F1, F1),
|
||||
expected: F2,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
if (ctx.canonical_name == "fmax" || ctx.canonical_name == "fmin")
|
||||
&& (input.0.is_nan() || input.1.is_nan())
|
||||
&& expected.is_nan()
|
||||
{
|
||||
return XFAIL;
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeOverride<(i32, f32)> for SpecialCase {
|
||||
fn check_float<F: Float>(
|
||||
input: (i32, f32),
|
||||
_actual: F,
|
||||
_expected: F,
|
||||
ulp: &mut u32,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
bessel_prec_dropoff(input, ulp, ctx)
|
||||
}
|
||||
}
|
||||
impl MaybeOverride<(i32, f64)> for SpecialCase {
|
||||
fn check_float<F: Float>(
|
||||
input: (i32, f64),
|
||||
_actual: F,
|
||||
_expected: F,
|
||||
ulp: &mut u32,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
bessel_prec_dropoff(input, ulp, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Our bessel functions blow up with large N values
|
||||
fn bessel_prec_dropoff<F: Float>(
|
||||
input: (i32, F),
|
||||
ulp: &mut u32,
|
||||
ctx: &CheckCtx,
|
||||
) -> Option<TestResult> {
|
||||
if ctx.canonical_name == "jn" {
|
||||
if input.0 > 4000 {
|
||||
return XFAIL;
|
||||
} else if input.0 > 2000 {
|
||||
// *ulp = 20_000;
|
||||
*ulp = 20000;
|
||||
} else if input.0 > 1000 {
|
||||
*ulp = 4000;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl MaybeOverride<(f32, f32, f32)> for SpecialCase {}
|
||||
impl MaybeOverride<(f64, f64, f64)> for SpecialCase {}
|
||||
impl MaybeOverride<(f32, i32)> for SpecialCase {}
|
||||
impl MaybeOverride<(f64, i32)> for SpecialCase {}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
//! Traits related to testing.
|
||||
//!
|
||||
//! There are three main traits in this module:
|
||||
//!
|
||||
//! - `GenerateInput`: implemented on any types that create test cases.
|
||||
//! - `TupleCall`: implemented on tuples to allow calling them as function arguments.
|
||||
//! - `CheckOutput`: implemented on anything that is an output type for validation against an
|
||||
//! expected value.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{Context, bail, ensure};
|
||||
|
||||
use crate::{Float, Hex, Int, MaybeOverride, SpecialCase, TestResult};
|
||||
|
||||
/// Implement this on types that can generate a sequence of tuples for test input.
|
||||
pub trait GenerateInput<TupleArgs> {
|
||||
fn get_cases(&self) -> impl Iterator<Item = TupleArgs>;
|
||||
}
|
||||
|
||||
/// Trait for calling a function with a tuple as arguments.
|
||||
///
|
||||
/// Implemented on the tuple with the function signature as the generic (so we can use the same
|
||||
/// tuple for multiple signatures).
|
||||
pub trait TupleCall<Func>: fmt::Debug {
|
||||
type Output;
|
||||
fn call(self, f: Func) -> Self::Output;
|
||||
}
|
||||
|
||||
/// Context passed to [`CheckOutput`].
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CheckCtx {
|
||||
/// Allowed ULP deviation
|
||||
pub ulp: u32,
|
||||
/// Function name.
|
||||
pub fname: &'static str,
|
||||
/// Return the unsuffixed version of the function name.
|
||||
pub canonical_name: &'static str,
|
||||
/// Source of truth for tests.
|
||||
pub basis: CheckBasis,
|
||||
}
|
||||
|
||||
impl CheckCtx {
|
||||
pub fn new(ulp: u32, fname: &'static str, basis: CheckBasis) -> Self {
|
||||
let canonical_fname = crate::canonical_name(fname);
|
||||
Self { ulp, fname, canonical_name: canonical_fname, basis }
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible items to test against
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum CheckBasis {
|
||||
/// Check against Musl's math sources.
|
||||
Musl,
|
||||
}
|
||||
|
||||
/// A trait to implement on any output type so we can verify it in a generic way.
|
||||
pub trait CheckOutput<Input>: Sized {
|
||||
/// Validate `self` (actual) and `expected` are the same.
|
||||
///
|
||||
/// `input` is only used here for error messages.
|
||||
fn validate<'a>(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult;
|
||||
}
|
||||
|
||||
impl<T1, R> TupleCall<fn(T1) -> R> for (T1,)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
{
|
||||
type Output = R;
|
||||
|
||||
fn call(self, f: fn(T1) -> R) -> Self::Output {
|
||||
f(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, R> TupleCall<fn(T1, T2) -> R> for (T1, T2)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
T2: fmt::Debug,
|
||||
{
|
||||
type Output = R;
|
||||
|
||||
fn call(self, f: fn(T1, T2) -> R) -> Self::Output {
|
||||
f(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, R> TupleCall<fn(T1, &mut T2) -> R> for (T1,)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
T2: fmt::Debug + Default,
|
||||
{
|
||||
type Output = (R, T2);
|
||||
|
||||
fn call(self, f: fn(T1, &mut T2) -> R) -> Self::Output {
|
||||
let mut t2 = T2::default();
|
||||
(f(self.0, &mut t2), t2)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3, R> TupleCall<fn(T1, T2, T3) -> R> for (T1, T2, T3)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
T2: fmt::Debug,
|
||||
T3: fmt::Debug,
|
||||
{
|
||||
type Output = R;
|
||||
|
||||
fn call(self, f: fn(T1, T2, T3) -> R) -> Self::Output {
|
||||
f(self.0, self.1, self.2)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3, R> TupleCall<fn(T1, T2, &mut T3) -> R> for (T1, T2)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
T2: fmt::Debug,
|
||||
T3: fmt::Debug + Default,
|
||||
{
|
||||
type Output = (R, T3);
|
||||
|
||||
fn call(self, f: fn(T1, T2, &mut T3) -> R) -> Self::Output {
|
||||
let mut t3 = T3::default();
|
||||
(f(self.0, self.1, &mut t3), t3)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3> TupleCall<fn(T1, &mut T2, &mut T3)> for (T1,)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
T2: fmt::Debug + Default,
|
||||
T3: fmt::Debug + Default,
|
||||
{
|
||||
type Output = (T2, T3);
|
||||
|
||||
fn call(self, f: fn(T1, &mut T2, &mut T3)) -> Self::Output {
|
||||
let mut t2 = T2::default();
|
||||
let mut t3 = T3::default();
|
||||
f(self.0, &mut t2, &mut t3);
|
||||
(t2, t3)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement for floats
|
||||
impl<F, Input> CheckOutput<Input> for F
|
||||
where
|
||||
F: Float + Hex,
|
||||
Input: Hex + fmt::Debug,
|
||||
u32: TryFrom<F::SignedInt, Error: fmt::Debug>,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
fn validate<'a>(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult {
|
||||
// Create a wrapper function so we only need to `.with_context` once.
|
||||
let inner = || -> TestResult {
|
||||
let mut allowed_ulp = ctx.ulp;
|
||||
|
||||
// If the tested function requires a nonstandard test, run it here.
|
||||
if let Some(res) =
|
||||
SpecialCase::check_float(input, self, expected, &mut allowed_ulp, ctx)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Check when both are NaNs
|
||||
if self.is_nan() && expected.is_nan() {
|
||||
// By default, NaNs have nothing special to check.
|
||||
return Ok(());
|
||||
} else if self.is_nan() || expected.is_nan() {
|
||||
// Check when only one is a NaN
|
||||
bail!("real value != NaN")
|
||||
}
|
||||
|
||||
// Make sure that the signs are the same before checing ULP to avoid wraparound
|
||||
let act_sig = self.signum();
|
||||
let exp_sig = expected.signum();
|
||||
ensure!(act_sig == exp_sig, "mismatched signs {act_sig} {exp_sig}");
|
||||
|
||||
if self.is_infinite() ^ expected.is_infinite() {
|
||||
bail!("mismatched infinities");
|
||||
}
|
||||
|
||||
let act_bits = self.to_bits().signed();
|
||||
let exp_bits = expected.to_bits().signed();
|
||||
|
||||
let ulp_diff = act_bits.checked_sub(exp_bits).unwrap().abs();
|
||||
|
||||
let ulp_u32 = u32::try_from(ulp_diff)
|
||||
.map_err(|e| anyhow::anyhow!("{e:?}: ulp of {ulp_diff} exceeds u32::MAX"))?;
|
||||
|
||||
ensure!(ulp_u32 <= allowed_ulp, "ulp {ulp_diff} > {allowed_ulp}",);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
inner().with_context(|| {
|
||||
format!(
|
||||
"\
|
||||
\n input: {input:?} {ibits}\
|
||||
\n expected: {expected:<22?} {expbits}\
|
||||
\n actual: {self:<22?} {actbits}\
|
||||
",
|
||||
actbits = self.hex(),
|
||||
expbits = expected.hex(),
|
||||
ibits = input.hex(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `CheckOutput` for combinations of types.
|
||||
macro_rules! impl_tuples {
|
||||
($(($a:ty, $b:ty);)*) => {
|
||||
$(
|
||||
impl<Input> CheckOutput<Input> for ($a, $b)
|
||||
where
|
||||
Input: Hex + fmt::Debug,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
fn validate<'a>(
|
||||
self,
|
||||
expected: Self,
|
||||
input: Input,
|
||||
ctx: &CheckCtx,
|
||||
) -> TestResult {
|
||||
self.0.validate(expected.0, input, ctx)
|
||||
.and_then(|()| self.1.validate(expected.1, input, ctx))
|
||||
.with_context(|| format!(
|
||||
"full context:\
|
||||
\n input: {input:?} {ibits}\
|
||||
\n expected: {expected:?} {expbits}\
|
||||
\n actual: {self:?} {actbits}\
|
||||
",
|
||||
actbits = self.hex(),
|
||||
expbits = expected.hex(),
|
||||
ibits = input.hex(),
|
||||
))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_tuples!(
|
||||
(f32, i32);
|
||||
(f64, i32);
|
||||
(f32, f32);
|
||||
(f64, f64);
|
||||
);
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
//! Ensure that `for_each_function!` isn't missing any symbols.
|
||||
|
||||
/// Files in `src/` that do not export a testable symbol.
|
||||
const ALLOWED_SKIPS: &[&str] = &[
|
||||
// Not a generic test function
|
||||
"fenv",
|
||||
// Nonpublic functions
|
||||
"expo2",
|
||||
"k_cos",
|
||||
"k_cosf",
|
||||
"k_expo2",
|
||||
"k_expo2f",
|
||||
"k_sin",
|
||||
"k_sinf",
|
||||
"k_tan",
|
||||
"k_tanf",
|
||||
"rem_pio2",
|
||||
"rem_pio2_large",
|
||||
"rem_pio2f",
|
||||
];
|
||||
|
||||
macro_rules! callback {
|
||||
(
|
||||
fn_name: $name:ident,
|
||||
CFn: $_CFn:ty,
|
||||
CArgs: $_CArgs:ty,
|
||||
CRet: $_CRet:ty,
|
||||
RustFn: $_RustFn:ty,
|
||||
RustArgs: $_RustArgs:ty,
|
||||
RustRet: $_RustRet:ty,
|
||||
extra: [$push_to:ident],
|
||||
) => {
|
||||
$push_to.push(stringify!($name));
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_for_each_function_all_included() {
|
||||
let mut included = Vec::new();
|
||||
let mut missing = Vec::new();
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: callback,
|
||||
extra: [included],
|
||||
};
|
||||
|
||||
for f in libm_test::ALL_FUNCTIONS {
|
||||
if !included.contains(f) && !ALLOWED_SKIPS.contains(f) {
|
||||
missing.push(f)
|
||||
}
|
||||
}
|
||||
|
||||
if !missing.is_empty() {
|
||||
panic!(
|
||||
"missing tests for the following: {missing:#?} \
|
||||
\nmake sure any new functions are entered in \
|
||||
`ALL_FUNCTIONS` (in `libm-macros`)."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
//! Compare our implementations with the result of musl functions, as provided by `musl-math-sys`.
|
||||
//!
|
||||
//! Currently this only tests randomized inputs. In the future this may be improved to test edge
|
||||
//! cases or run exhaustive tests.
|
||||
//!
|
||||
//! Note that musl functions do not always provide 0.5ULP rounding, so our functions can do better
|
||||
//! than these results.
|
||||
|
||||
// There are some targets we can't build musl for
|
||||
#![cfg(feature = "build-musl")]
|
||||
|
||||
use libm_test::gen::random;
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, TupleCall, musl_allowed_ulp};
|
||||
use musl_math_sys as musl;
|
||||
|
||||
macro_rules! musl_rand_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
attrs: [$($meta:meta)*]
|
||||
) => { paste::paste! {
|
||||
#[test]
|
||||
$(#[$meta])*
|
||||
fn [< musl_random_ $fn_name >]() {
|
||||
let fname = stringify!($fn_name);
|
||||
let ulp = musl_allowed_ulp(fname);
|
||||
let cases = random::get_test_cases::<$RustArgs>(fname);
|
||||
let ctx = CheckCtx::new(ulp, fname, CheckBasis::Musl);
|
||||
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl::$fn_name as $CFn);
|
||||
let crate_res = input.call(libm::$fn_name as $RustFn);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: musl_rand_tests,
|
||||
skip: [],
|
||||
attributes: [
|
||||
#[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586
|
||||
[exp10, exp10f, exp2, exp2f, rint]
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "musl-math-sys"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
libm = { path = "../../" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.1.24"
|
||||
328
library/compiler-builtins/libm/crates/musl-math-sys/build.rs
Normal file
328
library/compiler-builtins/libm/crates/musl-math-sys/build.rs
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
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();
|
||||
|
||||
// Default to the `{workspace_root}/musl` if not specified
|
||||
let musl_dir = env::var("MUSL_SOURCE_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| manifest_dir.parent().unwrap().parent().unwrap().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-env-changed=MUSL_SOURCE_DIR");
|
||||
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;
|
||||
assert!(
|
||||
musl_dir.exists(),
|
||||
"musl source is missing. it can be downloaded with ./ci/download-musl.sh"
|
||||
);
|
||||
|
||||
let math = musl_dir.join("src/math");
|
||||
let arch_dir = musl_dir.join("arch").join(&cfg.musl_arch);
|
||||
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")
|
||||
.opt_level(3)
|
||||
.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:#?}");
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
279
library/compiler-builtins/libm/crates/musl-math-sys/src/lib.rs
Normal file
279
library/compiler-builtins/libm/crates/musl-math-sys/src/lib.rs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
//! 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 {
|
||||
( $(
|
||||
$pfx_name:ident: $name:ident( $($arg:ident: $aty:ty),+ ) -> $rty:ty;
|
||||
)* ) => {
|
||||
extern "C" {
|
||||
$( fn $pfx_name( $($arg: $aty),+ ) -> $rty; )*
|
||||
}
|
||||
|
||||
$(
|
||||
// Expose a safe version
|
||||
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;
|
||||
musl_nan: nan(a: *const c_char) -> f64;
|
||||
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_ynf: ynf(a: c_int, b: f32) -> f32;
|
||||
}
|
||||
|
|
@ -194,6 +194,8 @@ mod tests {
|
|||
use super::rem_pio2;
|
||||
|
||||
#[test]
|
||||
// FIXME(correctness): inaccurate results on i586
|
||||
#[cfg_attr(all(target_arch = "x86", not(target_feature = "sse")), ignore)]
|
||||
fn test_near_pi() {
|
||||
let arg = 3.141592025756836;
|
||||
let arg = force_eval!(arg);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue