Merge from rustc

This commit is contained in:
The Miri Cronjob Bot 2025-06-06 05:02:49 +00:00
commit c44bc10b67
291 changed files with 4610 additions and 2821 deletions

View file

@ -5,7 +5,7 @@ on:
concurrency:
# Make sure that new pushes cancel running jobs
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
@ -13,7 +13,7 @@ env:
RUSTDOCFLAGS: -Dwarnings
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: full
BENCHMARK_RUSTC: nightly-2025-01-16 # Pin the toolchain for reproducable results
BENCHMARK_RUSTC: nightly-2025-05-28 # Pin the toolchain for reproducable results
jobs:
# Determine which tests should be run based on changed files.
@ -108,8 +108,6 @@ jobs:
- name: Print runner information
run: uname -a
- uses: actions/checkout@v4
with:
submodules: true
- name: Install Rust (rustup)
shell: bash
run: |
@ -119,7 +117,6 @@ jobs:
rustup update "$channel" --no-self-update
rustup default "$channel"
rustup target add "${{ matrix.target }}"
rustup component add llvm-tools-preview
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
with:
@ -147,6 +144,10 @@ jobs:
shell: bash
- run: echo "RUST_COMPILER_RT_ROOT=$(realpath ./compiler-rt)" >> "$GITHUB_ENV"
shell: bash
- name: Download musl source
run: ./ci/update-musl.sh
shell: bash
- name: Verify API list
if: matrix.os == 'ubuntu-24.04'
@ -183,8 +184,6 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
submodules: true
# Unlike rustfmt, stable clippy does not work on code with nightly features.
- name: Install nightly `clippy`
run: |
@ -192,16 +191,22 @@ jobs:
rustup default nightly
rustup component add clippy
- uses: Swatinem/rust-cache@v2
- name: Download musl source
run: ./ci/update-musl.sh
- run: cargo clippy --workspace --all-targets
benchmarks:
name: Benchmarks
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-24.04
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@master
with:
submodules: true
- uses: taiki-e/install-action@cargo-binstall
- name: Set up dependencies
@ -216,12 +221,16 @@ jobs:
cargo binstall -y iai-callgrind-runner --version "$iai_version"
sudo apt-get install valgrind
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Download musl source
run: ./ci/update-musl.sh
- name: Run icount benchmarks
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: ./ci/bench-icount.sh
run: ./ci/bench-icount.sh ${{ matrix.target }}
- name: Upload the benchmark baseline
uses: actions/upload-artifact@v4
@ -249,8 +258,6 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install Rust (rustup)
run: rustup update nightly --no-self-update && rustup default nightly
shell: bash
@ -285,8 +292,6 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install stable `rustfmt`
run: rustup set profile minimal && rustup default stable && rustup component add rustfmt
- run: cargo fmt -- --check
@ -310,13 +315,13 @@ jobs:
TO_TEST: ${{ matrix.to_test }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install Rust
run: |
rustup update nightly --no-self-update
rustup default nightly
- uses: Swatinem/rust-cache@v2
- name: download musl source
run: ./ci/update-musl.sh
- name: Run extensive tests
run: ./ci/run-extensive.sh
- name: Print test logs if available

View file

@ -14,3 +14,6 @@ iai-home
*.bk
*.rs.bk
.#*
# Manually managed
crates/musl-math-sys/musl

View file

@ -1,4 +0,0 @@
[submodule "crates/musl-math-sys/musl"]
path = crates/musl-math-sys/musl
url = https://git.musl-libc.org/git/musl
shallow = true

View file

@ -3,9 +3,11 @@ resolver = "2"
members = [
"builtins-test",
"compiler-builtins",
"crates/josh-sync",
"crates/libm-macros",
"crates/musl-math-sys",
"crates/panic-handler",
"crates/symbol-check",
"crates/util",
"libm",
"libm-test",

View file

@ -5,7 +5,7 @@ This repository contains two main crates:
* `compiler-builtins`: symbols that the compiler expects to be available at
link time
* `libm`: a Rust implementation of C math libraries, used to provide
implementations in `ocre`.
implementations in `core`.
More details are at [compiler-builtins/README.md](compiler-builtins/README.md)
and [libm/README.md](libm/README.md).

View file

@ -1,12 +1,12 @@
[package]
name = "builtins-test-intrinsics"
version = "0.1.0"
edition = "2021"
edition = "2024"
publish = false
license = "MIT OR Apache-2.0"
[dependencies]
compiler_builtins = { path = "../compiler-builtins", features = ["compiler-builtins"]}
compiler_builtins = { path = "../compiler-builtins", features = ["compiler-builtins"] }
panic-handler = { path = "../crates/panic-handler" }
[features]

View file

@ -13,11 +13,14 @@
#![no_std]
#![no_main]
// Ensure this `compiler_builtins` gets used, rather than the version injected from the sysroot.
extern crate compiler_builtins;
extern crate panic_handler;
// SAFETY: no definitions, only used for linking
#[cfg(all(not(thumb), not(windows), not(target_arch = "wasm32")))]
#[link(name = "c")]
extern "C" {}
unsafe extern "C" {}
// Every function in this module maps will be lowered to an intrinsic by LLVM, if the platform
// doesn't have native support for the operation used in the function. ARM has a naming convention
@ -651,22 +654,23 @@ fn something_with_a_dtor(f: &dyn Fn()) {
#[unsafe(no_mangle)]
#[cfg(not(thumb))]
fn main(_argc: core::ffi::c_int, _argv: *const *const u8) -> core::ffi::c_int {
extern "C" fn main(_argc: core::ffi::c_int, _argv: *const *const u8) -> core::ffi::c_int {
run();
0
}
#[unsafe(no_mangle)]
#[cfg(thumb)]
pub fn _start() -> ! {
extern "C" fn _start() -> ! {
run();
loop {}
}
// SAFETY: no definitions, only used for linking
#[cfg(windows)]
#[link(name = "kernel32")]
#[link(name = "msvcrt")]
extern "C" {}
unsafe extern "C" {}
// ARM targets need these symbols
#[unsafe(no_mangle)]

View file

@ -10,11 +10,11 @@ license = "MIT AND Apache-2.0 WITH LLVM-exception AND (MIT OR Apache-2.0)"
# For fuzzing tests we want a deterministic seedable RNG. We also eliminate potential
# problems with system RNGs on the variety of platforms this crate is tested on.
# `xoshiro128**` is used for its quality, size, and speed at generating `u32` shift amounts.
rand_xoshiro = "0.6"
rand_xoshiro = "0.7"
# To compare float builtins against
rustc_apfloat = "0.2.1"
rustc_apfloat = "0.2.2"
# Really a dev dependency, but dev dependencies can't be optional
iai-callgrind = { version = "0.14.0", optional = true }
iai-callgrind = { version = "0.14.1", optional = true }
[dependencies.compiler_builtins]
path = "../compiler-builtins"
@ -22,7 +22,7 @@ default-features = false
features = ["unstable-public-internals"]
[dev-dependencies]
criterion = { version = "0.5.1", default-features = false, features = ["cargo_bench_support"] }
criterion = { version = "0.6.0", default-features = false, features = ["cargo_bench_support"] }
paste = "1.0.15"
[target.'cfg(all(target_arch = "arm", not(any(target_env = "gnu", target_env = "musl")), target_os = "linux"))'.dev-dependencies]

View file

@ -1,12 +1,23 @@
#![cfg_attr(f128_enabled, feature(f128))]
use builtins_test::float_bench;
use compiler_builtins::float::cmp;
use compiler_builtins::float::cmp::{self, CmpResult};
use criterion::{Criterion, criterion_main};
/// `gt` symbols are allowed to return differing results, they just get compared
/// to 0.
fn gt_res_eq(a: i32, b: i32) -> bool {
fn gt_res_eq(mut a: CmpResult, mut b: CmpResult) -> bool {
// FIXME: Our CmpResult used to be `i32`, but GCC/LLVM expect `isize`. on 64-bit platforms,
// this means the top half of the word may be garbage if built with an old version of
// `compiler-builtins`, so add a hack around this.
//
// This can be removed once a version of `compiler-builtins` with the return type fix makes
// it upstream.
if size_of::<CmpResult>() == 8 {
a = a as i32 as CmpResult;
b = b as i32 as CmpResult;
}
let a_lt_0 = a <= 0;
let b_lt_0 = b <= 0;
(a_lt_0 && b_lt_0) || (!a_lt_0 && !b_lt_0)
@ -14,14 +25,14 @@ fn gt_res_eq(a: i32, b: i32) -> bool {
float_bench! {
name: cmp_f32_gt,
sig: (a: f32, b: f32) -> i32,
sig: (a: f32, b: f32) -> CmpResult,
crate_fn: cmp::__gtsf2,
sys_fn: __gtsf2,
sys_available: all(),
output_eq: gt_res_eq,
asm: [
#[cfg(target_arch = "x86_64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"xor {ret:e}, {ret:e}",
"ucomiss {a}, {b}",
@ -36,7 +47,7 @@ float_bench! {
};
#[cfg(target_arch = "aarch64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"fcmp {a:s}, {b:s}",
"cset {ret:w}, gt",
@ -53,13 +64,13 @@ float_bench! {
float_bench! {
name: cmp_f32_unord,
sig: (a: f32, b: f32) -> i32,
sig: (a: f32, b: f32) -> CmpResult,
crate_fn: cmp::__unordsf2,
sys_fn: __unordsf2,
sys_available: all(),
asm: [
#[cfg(target_arch = "x86_64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"xor {ret:e}, {ret:e}",
"ucomiss {a}, {b}",
@ -74,7 +85,7 @@ float_bench! {
};
#[cfg(target_arch = "aarch64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"fcmp {a:s}, {b:s}",
"cset {ret:w}, vs",
@ -91,14 +102,14 @@ float_bench! {
float_bench! {
name: cmp_f64_gt,
sig: (a: f64, b: f64) -> i32,
sig: (a: f64, b: f64) -> CmpResult,
crate_fn: cmp::__gtdf2,
sys_fn: __gtdf2,
sys_available: all(),
output_eq: gt_res_eq,
asm: [
#[cfg(target_arch = "x86_64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"xor {ret:e}, {ret:e}",
"ucomisd {a}, {b}",
@ -113,7 +124,7 @@ float_bench! {
};
#[cfg(target_arch = "aarch64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"fcmp {a:d}, {b:d}",
"cset {ret:w}, gt",
@ -130,13 +141,13 @@ float_bench! {
float_bench! {
name: cmp_f64_unord,
sig: (a: f64, b: f64) -> i32,
sig: (a: f64, b: f64) -> CmpResult,
crate_fn: cmp::__unorddf2,
sys_fn: __unorddf2,
sys_available: all(),
asm: [
#[cfg(target_arch = "x86_64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"xor {ret:e}, {ret:e}",
"ucomisd {a}, {b}",
@ -151,7 +162,7 @@ float_bench! {
};
#[cfg(target_arch = "aarch64")] {
let ret: i32;
let ret: CmpResult;
asm!(
"fcmp {a:d}, {b:d}",
"cset {ret:w}, vs",
@ -168,7 +179,7 @@ float_bench! {
float_bench! {
name: cmp_f128_gt,
sig: (a: f128, b: f128) -> i32,
sig: (a: f128, b: f128) -> CmpResult,
crate_fn: cmp::__gttf2,
crate_fn_ppc: cmp::__gtkf2,
sys_fn: __gttf2,
@ -180,7 +191,7 @@ float_bench! {
float_bench! {
name: cmp_f128_unord,
sig: (a: f128, b: f128) -> i32,
sig: (a: f128, b: f128) -> CmpResult,
crate_fn: cmp::__unordtf2,
crate_fn_ppc: cmp::__unordkf2,
sys_fn: __unordtf2,

View file

@ -358,8 +358,8 @@ impl_testio!(float f16);
impl_testio!(float f32, f64);
#[cfg(f128_enabled)]
impl_testio!(float f128);
impl_testio!(int i16, i32, i64, i128);
impl_testio!(int u16, u32, u64, u128);
impl_testio!(int i8, i16, i32, i64, i128, isize);
impl_testio!(int u8, u16, u32, u64, u128, usize);
impl_testio!((float, int)(f32, i32));
impl_testio!((float, int)(f64, i32));
#[cfg(f128_enabled)]

View file

@ -40,6 +40,75 @@ pub const N: u32 = if cfg!(target_arch = "x86_64") && !cfg!(debug_assertions) {
10_000
};
/// Additional constants that determine how the integer gets fuzzed.
trait FuzzInt: MinInt {
/// LUT used for maximizing the space covered and minimizing the computational cost of fuzzing
/// in `builtins-test`. For example, Self = u128 produces [0,1,2,7,8,15,16,31,32,63,64,95,96,
/// 111,112,119,120,125,126,127].
const FUZZ_LENGTHS: [u8; 20] = make_fuzz_lengths(Self::BITS);
/// The number of entries of `FUZZ_LENGTHS` actually used. The maximum is 20 for u128.
const FUZZ_NUM: usize = {
let log2 = Self::BITS.ilog2() as usize;
if log2 == 3 {
// case for u8
6
} else {
// 3 entries on each extreme, 2 in the middle, and 4 for each scale of intermediate
// boundaries.
8 + (4 * (log2 - 4))
}
};
}
impl<I> FuzzInt for I where I: MinInt {}
const fn make_fuzz_lengths(bits: u32) -> [u8; 20] {
let mut v = [0u8; 20];
v[0] = 0;
v[1] = 1;
v[2] = 2; // important for parity and the iX::MIN case when reversed
let mut i = 3;
// No need for any more until the byte boundary, because there should be no algorithms
// that are sensitive to anything not next to byte boundaries after 2. We also scale
// in powers of two, which is important to prevent u128 corner tests from getting too
// big.
let mut l = 8;
loop {
if l >= ((bits / 2) as u8) {
break;
}
// get both sides of the byte boundary
v[i] = l - 1;
i += 1;
v[i] = l;
i += 1;
l *= 2;
}
if bits != 8 {
// add the lower side of the middle boundary
v[i] = ((bits / 2) - 1) as u8;
i += 1;
}
// We do not want to jump directly from the Self::BITS/2 boundary to the Self::BITS
// boundary because of algorithms that split the high part up. We reverse the scaling
// as we go to Self::BITS.
let mid = i;
let mut j = 1;
loop {
v[i] = (bits as u8) - (v[mid - j]) - 1;
if j == mid {
break;
}
i += 1;
j += 1;
}
v
}
/// Random fuzzing step. When run several times, it results in excellent fuzzing entropy such as:
/// 11110101010101011110111110011111
/// 10110101010100001011101011001010
@ -92,10 +161,9 @@ fn fuzz_step<I: Int>(rng: &mut Xoshiro128StarStar, x: &mut I) {
macro_rules! edge_cases {
($I:ident, $case:ident, $inner:block) => {
for i0 in 0..$I::FUZZ_NUM {
let mask_lo = (!$I::UnsignedInt::ZERO).wrapping_shr($I::FUZZ_LENGTHS[i0] as u32);
let mask_lo = (!$I::Unsigned::ZERO).wrapping_shr($I::FUZZ_LENGTHS[i0] as u32);
for i1 in i0..I::FUZZ_NUM {
let mask_hi =
(!$I::UnsignedInt::ZERO).wrapping_shl($I::FUZZ_LENGTHS[i1 - i0] as u32);
let mask_hi = (!$I::Unsigned::ZERO).wrapping_shl($I::FUZZ_LENGTHS[i1 - i0] as u32);
let $case = I::from_unsigned(mask_lo & mask_hi);
$inner
}
@ -107,7 +175,7 @@ macro_rules! edge_cases {
/// edge cases, followed by a more random fuzzer that runs `n` times.
pub fn fuzz<I: Int, F: FnMut(I)>(n: u32, mut f: F)
where
<I as MinInt>::UnsignedInt: Int,
<I as MinInt>::Unsigned: Int,
{
// edge case tester. Calls `f` 210 times for u128.
// zero gets skipped by the loop
@ -128,7 +196,7 @@ where
/// The same as `fuzz`, except `f` has two inputs.
pub fn fuzz_2<I: Int, F: Fn(I, I)>(n: u32, f: F)
where
<I as MinInt>::UnsignedInt: Int,
<I as MinInt>::Unsigned: Int,
{
// Check cases where the first and second inputs are zero. Both call `f` 210 times for `u128`.
edge_cases!(I, case, {

View file

@ -24,7 +24,8 @@ macro_rules! panic {
};
}
extern "C" {
// SAFETY: defined in compiler-builtins
unsafe extern "aapcs" {
fn __aeabi_memclr4(dest: *mut u8, n: usize);
fn __aeabi_memset4(dest: *mut u8, n: usize, c: u32);
}

View file

@ -22,7 +22,8 @@ macro_rules! panic {
};
}
extern "C" {
// SAFETY: defined in compiler-builtins
unsafe extern "aapcs" {
fn __aeabi_memcpy(dest: *mut u8, src: *const u8, n: usize);
fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, n: usize);
}

View file

@ -24,7 +24,8 @@ macro_rules! panic {
};
}
extern "C" {
// SAFETY: defined in compiler-builtins
unsafe extern "aapcs" {
fn __aeabi_memset4(dest: *mut u8, n: usize, c: u32);
}

View file

@ -58,8 +58,6 @@ pow! {
}
#[cfg(f128_enabled)]
// FIXME(f16_f128): MSVC cannot build these until `__divtf3` is available in nightly.
#[cfg(not(target_env = "msvc"))]
#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
pow! {
f128, 1e-36, __powitf2, not(feature = "no-sys-f128");

View file

@ -2,10 +2,21 @@
set -eux
target="${1:-}"
if [ -z "$target" ]; then
host_target=$(rustc -vV | awk '/^host/ { print $2 }')
echo "Defaulted to host target $host_target"
target="$host_target"
fi
iai_home="iai-home"
# Use the arch as a tag to disambiguate artifacts
tag="$(echo "$target" | cut -d'-' -f1)"
# Download the baseline from master
./ci/ci-util.py locate-baseline --download --extract
./ci/ci-util.py locate-baseline --download --extract --tag "$tag"
# Run benchmarks once
function run_icount_benchmarks() {
@ -35,16 +46,18 @@ function run_icount_benchmarks() {
shift
done
# Run iai-callgrind benchmarks
cargo bench "${cargo_args[@]}" -- "${iai_args[@]}"
# Run iai-callgrind benchmarks. Do this in a subshell with `&& true` to
# capture rather than exit on error.
(cargo bench "${cargo_args[@]}" -- "${iai_args[@]}") && true
exit_code="$?"
# NB: iai-callgrind should exit on error but does not, so we inspect the sumary
# for errors. See https://github.com/iai-callgrind/iai-callgrind/issues/337
if [ -n "${PR_NUMBER:-}" ]; then
# If this is for a pull request, ignore regressions if specified.
./ci/ci-util.py check-regressions --home "$iai_home" --allow-pr-override "$PR_NUMBER"
if [ "$exit_code" -eq 0 ]; then
echo "Benchmarks completed with no regressions"
elif [ -z "${PR_NUMBER:-}" ]; then
# Disregard regressions after merge
echo "Benchmarks completed with regressions; ignoring (not in a PR)"
else
./ci/ci-util.py check-regressions --home "$iai_home" || true
./ci/ci-util.py handle-banch-regressions "$PR_NUMBER"
fi
}
@ -53,6 +66,6 @@ run_icount_benchmarks --features force-soft-floats -- --save-baseline=softfloat
run_icount_benchmarks -- --save-baseline=hardfloat
# Name and tar the new baseline
name="baseline-icount-$(date -u +'%Y%m%d%H%M')-${GITHUB_SHA:0:12}"
name="baseline-icount-$tag-$(date -u +'%Y%m%d%H%M')-${GITHUB_SHA:0:12}"
echo "BASELINE_NAME=$name" >>"$GITHUB_ENV"
tar cJf "$name.tar.xz" "$iai_home"

View file

@ -11,7 +11,7 @@ import re
import subprocess as sp
import sys
from dataclasses import dataclass
from glob import glob, iglob
from glob import glob
from inspect import cleandoc
from os import getenv
from pathlib import Path
@ -28,21 +28,20 @@ USAGE = cleandoc(
Calculate a matrix of which functions had source change, print that as
a JSON object.
locate-baseline [--download] [--extract]
locate-baseline [--download] [--extract] [--tag TAG]
Locate the most recent benchmark baseline available in CI and, if flags
specify, download and extract it. Never exits with nonzero status if
downloading fails.
`--tag` can be specified to look for artifacts with a specific tag, such as
for a specific architecture.
Note that `--extract` will overwrite files in `iai-home`.
check-regressions [--home iai-home] [--allow-pr-override pr_number]
Check `iai-home` (or `iai-home` if unspecified) for `summary.json`
files and see if there are any regressions. This is used as a workaround
for `iai-callgrind` not exiting with error status; see
<https://github.com/iai-callgrind/iai-callgrind/issues/337>.
If `--allow-pr-override` is specified, the regression check will not exit
with failure if any line in the PR starts with `allow-regressions`.
handle-bench-regressions PR_NUMBER
Exit with success if the pull request contains a line starting with
`ci: allow-regressions`, indicating that regressions in benchmarks should
be accepted. Otherwise, exit 1.
"""
)
@ -50,7 +49,7 @@ REPO_ROOT = Path(__file__).parent.parent
GIT = ["git", "-C", REPO_ROOT]
DEFAULT_BRANCH = "master"
WORKFLOW_NAME = "CI" # Workflow that generates the benchmark artifacts
ARTIFACT_GLOB = "baseline-icount*"
ARTIFACT_PREFIX = "baseline-icount*"
# Place this in a PR body to skip regression checks (must be at the start of a line).
REGRESSION_DIRECTIVE = "ci: allow-regressions"
# Place this in a PR body to skip extensive tests
@ -278,6 +277,7 @@ def locate_baseline(flags: list[str]) -> None:
download = False
extract = False
tag = ""
while len(flags) > 0:
match flags[0]:
@ -285,6 +285,9 @@ def locate_baseline(flags: list[str]) -> None:
download = True
case "--extract":
extract = True
case "--tag":
tag = flags[1]
flags = flags[1:]
case _:
eprint(USAGE)
exit(1)
@ -333,8 +336,10 @@ def locate_baseline(flags: list[str]) -> None:
eprint("skipping download step")
return
artifact_glob = f"{ARTIFACT_PREFIX}{f"-{tag}" if tag else ""}*"
sp.run(
["gh", "run", "download", str(job_id), f"--pattern={ARTIFACT_GLOB}"],
["gh", "run", "download", str(job_id), f"--pattern={artifact_glob}"],
check=False,
)
@ -344,7 +349,7 @@ def locate_baseline(flags: list[str]) -> None:
# Find the baseline with the most recent timestamp. GH downloads the files to e.g.
# `some-dirname/some-dirname.tar.xz`, so just glob the whole thing together.
candidate_baselines = glob(f"{ARTIFACT_GLOB}/{ARTIFACT_GLOB}")
candidate_baselines = glob(f"{artifact_glob}/{artifact_glob}")
if len(candidate_baselines) == 0:
eprint("no possible baseline directories found")
return
@ -356,64 +361,22 @@ def locate_baseline(flags: list[str]) -> None:
eprint("baseline extracted successfully")
def check_iai_regressions(args: list[str]):
"""Find regressions in iai summary.json files, exit with failure if any are
found.
"""
def handle_bench_regressions(args: list[str]):
"""Exit with error unless the PR message contains an ignore directive."""
iai_home_str = "iai-home"
pr_number = None
match args:
case [pr_number]:
pr_number = pr_number
case _:
eprint(USAGE)
exit(1)
while len(args) > 0:
match args:
case ["--home", home, *rest]:
iai_home_str = home
args = rest
case ["--allow-pr-override", pr_num, *rest]:
pr_number = pr_num
args = rest
case _:
eprint(USAGE)
exit(1)
iai_home = Path(iai_home_str)
found_summaries = False
regressions: list[dict] = []
for summary_path in iglob("**/summary.json", root_dir=iai_home, recursive=True):
found_summaries = True
with open(iai_home / summary_path, "r") as f:
summary = json.load(f)
summary_regs = []
run = summary["callgrind_summary"]["callgrind_run"]
fname = summary["function_name"]
id = summary["id"]
name_entry = {"name": f"{fname}.{id}"}
for segment in run["segments"]:
summary_regs.extend(segment["regressions"])
summary_regs.extend(run["total"]["regressions"])
regressions.extend(name_entry | reg for reg in summary_regs)
if not found_summaries:
eprint(f"did not find any summary.json files within {iai_home}")
exit(1)
if len(regressions) == 0:
eprint("No regressions found")
pr = PrInfo.load(pr_number)
if pr.contains_directive(REGRESSION_DIRECTIVE):
eprint("PR allows regressions")
return
eprint("Found regressions:", json.dumps(regressions, indent=4))
if pr_number is not None:
pr = PrInfo.load(pr_number)
if pr.contains_directive(REGRESSION_DIRECTIVE):
eprint("PR allows regressions, returning")
return
eprint("Regressions were found; benchmark failed")
exit(1)
@ -424,8 +387,8 @@ def main():
ctx.emit_workflow_output()
case ["locate-baseline", *flags]:
locate_baseline(flags)
case ["check-regressions", *args]:
check_iai_regressions(args)
case ["handle-bench-regressions", *args]:
handle_bench_regressions(args)
case ["--help" | "-h"]:
print(USAGE)
exit()

View file

@ -47,130 +47,49 @@ else
fi
fi
# Ensure there are no duplicate symbols or references to `core` when
# `compiler-builtins` is built with various features. Symcheck invokes Cargo to
# build with the arguments we provide it, then validates the built artifacts.
symcheck=(cargo run -p symbol-check --release)
[[ "$target" = "wasm"* ]] && symcheck+=(--features wasm)
symcheck+=(-- build-and-check)
declare -a rlib_paths
"${symcheck[@]}" -p compiler_builtins --target "$target"
"${symcheck[@]}" -p compiler_builtins --target "$target" --release
"${symcheck[@]}" -p compiler_builtins --target "$target" --features c
"${symcheck[@]}" -p compiler_builtins --target "$target" --features c --release
"${symcheck[@]}" -p compiler_builtins --target "$target" --features no-asm
"${symcheck[@]}" -p compiler_builtins --target "$target" --features no-asm --release
"${symcheck[@]}" -p compiler_builtins --target "$target" --features no-f16-f128
"${symcheck[@]}" -p compiler_builtins --target "$target" --features no-f16-f128 --release
# Set the `rlib_paths` global array to a list of all compiler-builtins rlibs
update_rlib_paths() {
if [ -d /builtins-target ]; then
rlib_paths=( /builtins-target/"${target}"/debug/deps/libcompiler_builtins-*.rlib )
else
rlib_paths=( target/"${target}"/debug/deps/libcompiler_builtins-*.rlib )
fi
}
# Remove any existing artifacts from previous tests that don't set #![compiler_builtins]
update_rlib_paths
rm -f "${rlib_paths[@]}"
cargo build -p compiler_builtins --target "$target"
cargo build -p compiler_builtins --target "$target" --release
cargo build -p compiler_builtins --target "$target" --features c
cargo build -p compiler_builtins --target "$target" --features c --release
cargo build -p compiler_builtins --target "$target" --features no-asm
cargo build -p compiler_builtins --target "$target" --features no-asm --release
cargo build -p compiler_builtins --target "$target" --features no-f16-f128
cargo build -p compiler_builtins --target "$target" --features no-f16-f128 --release
PREFIX=${target//unknown-/}-
case "$target" in
armv7-*)
PREFIX=arm-linux-gnueabihf-
;;
thumb*)
PREFIX=arm-none-eabi-
;;
*86*-*)
PREFIX=
;;
esac
NM=$(find "$(rustc --print sysroot)" \( -name llvm-nm -o -name llvm-nm.exe \) )
if [ "$NM" = "" ]; then
NM="${PREFIX}nm"
fi
# i686-pc-windows-gnu tools have a dependency on some DLLs, so run it with
# rustup run to ensure that those are in PATH.
TOOLCHAIN="$(rustup show active-toolchain | sed 's/ (default)//')"
if [[ "$TOOLCHAIN" == *i686-pc-windows-gnu ]]; then
NM="rustup run $TOOLCHAIN $NM"
fi
# Look out for duplicated symbols when we include the compiler-rt (C) implementation
update_rlib_paths
for rlib in "${rlib_paths[@]}"; do
set +x
echo "================================================================"
echo "checking $rlib for duplicate symbols"
echo "================================================================"
set -x
duplicates_found=0
# NOTE On i586, It's normal that the get_pc_thunk symbol appears several
# times so ignore it
$NM -g --defined-only "$rlib" 2>&1 |
sort |
uniq -d |
grep -v __x86.get_pc_thunk --quiet |
grep 'T __' && duplicates_found=1
if [ "$duplicates_found" != 0 ]; then
echo "error: found duplicate symbols"
exit 1
else
echo "success; no duplicate symbols found"
fi
done
rm -f "${rlib_paths[@]}"
build_intrinsics_test() {
cargo build \
run_intrinsics_test() {
args=(
--target "$target" --verbose \
--manifest-path builtins-test-intrinsics/Cargo.toml "$@"
--manifest-path builtins-test-intrinsics/Cargo.toml
)
args+=( "$@" )
# symcheck also checks the results of builtins-test-intrinsics
"${symcheck[@]}" "${args[@]}"
# FIXME: we get access violations on Windows, our entrypoint may need to
# be tweaked.
if [ "${BUILD_ONLY:-}" != "1" ] && ! [[ "$target" = *"windows"* ]]; then
cargo run "${args[@]}"
fi
}
# Verify that we haven't dropped any intrinsics/symbols
build_intrinsics_test
build_intrinsics_test --release
build_intrinsics_test --features c
build_intrinsics_test --features c --release
run_intrinsics_test
run_intrinsics_test --release
run_intrinsics_test --features c
run_intrinsics_test --features c --release
# Verify that there are no undefined symbols to `panic` within our
# implementations
CARGO_PROFILE_DEV_LTO=true build_intrinsics_test
CARGO_PROFILE_RELEASE_LTO=true build_intrinsics_test --release
# Ensure no references to any symbols from core
update_rlib_paths
for rlib in "${rlib_paths[@]}"; do
set +x
echo "================================================================"
echo "checking $rlib for references to core"
echo "================================================================"
set -x
tmpdir="${CARGO_TARGET_DIR:-target}/tmp"
test -d "$tmpdir" || mkdir "$tmpdir"
defined="$tmpdir/defined_symbols.txt"
undefined="$tmpdir/defined_symbols.txt"
$NM --quiet -U "$rlib" | grep 'T _ZN4core' | awk '{print $3}' | sort | uniq > "$defined"
$NM --quiet -u "$rlib" | grep 'U _ZN4core' | awk '{print $2}' | sort | uniq > "$undefined"
grep_has_results=0
grep -v -F -x -f "$defined" "$undefined" && grep_has_results=1
if [ "$target" = "powerpc64-unknown-linux-gnu" ]; then
echo "FIXME: powerpc64 fails these tests"
elif [ "$grep_has_results" != 0 ]; then
echo "error: found unexpected references to core"
exit 1
else
echo "success; no references to core found"
fi
done
CARGO_PROFILE_DEV_LTO=true run_intrinsics_test
CARGO_PROFILE_RELEASE_LTO=true run_intrinsics_test --release
# Test libm

View file

@ -0,0 +1,15 @@
#!/bin/sh
# Download musl to a repository for `musl-math-sys`
set -eux
url=git://git.musl-libc.org/musl
ref=c47ad25ea3b484e10326f933e927c0bc8cded3da
dst=crates/musl-math-sys/musl
if ! [ -d "$dst" ]; then
git clone "$url" "$dst" --single-branch --depth=1000
fi
git -C "$dst" fetch "$url" --depth=1
git -C "$dst" checkout "$ref"

View file

@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.1.160](https://github.com/rust-lang/compiler-builtins/compare/compiler_builtins-v0.1.159...compiler_builtins-v0.1.160) - 2025-05-29
### Other
- Change `compiler-builtins` to edition 2024
- Remove unneeded C symbols
- Reuse `libm`'s `Caat` and `CastFrom` in `compiler-builtins`
- Reuse `MinInt` and `Int` from `libm` in `compiler-builtins`
- Update `CmpResult` to use a pointer-sized return type
- Enable `__powitf2` on MSVC
- Fix `i256::MAX`
- Add a note saying why we use `frintx` rather than `frintn`
- Typo in README.md
- Clean up unused files
## [0.1.159](https://github.com/rust-lang/compiler-builtins/compare/compiler_builtins-v0.1.158...compiler_builtins-v0.1.159) - 2025-05-12
### Other

View file

@ -1,13 +1,13 @@
[package]
authors = ["Jorge Aparicio <japaricious@gmail.com>"]
name = "compiler_builtins"
version = "0.1.159"
version = "0.1.160"
license = "MIT AND Apache-2.0 WITH LLVM-exception AND (MIT OR Apache-2.0)"
readme = "README.md"
repository = "https://github.com/rust-lang/compiler-builtins"
homepage = "https://github.com/rust-lang/compiler-builtins"
documentation = "https://docs.rs/compiler_builtins"
edition = "2021"
edition = "2024"
description = "Compiler intrinsics used by the Rust compiler."
links = "compiler-rt"
@ -19,13 +19,10 @@ test = false
[dependencies]
# For more information on this dependency see
# https://github.com/rust-lang/rust/tree/master/library/rustc-std-workspace-core
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
core = { version = "1.0.1", optional = true, package = "rustc-std-workspace-core" }
[build-dependencies]
cc = { optional = true, version = "1.0" }
[dev-dependencies]
panic-handler = { path = "../crates/panic-handler" }
cc = { optional = true, version = "1.2" }
[features]
default = ["compiler-builtins"]

View file

@ -555,7 +555,6 @@ mod c {
if (target.arch == "aarch64" || target.arch == "arm64ec") && consider_float_intrinsics {
sources.extend(&[
("__comparetf2", "comparetf2.c"),
("__fe_getround", "fp_mode.c"),
("__fe_raise_inexact", "fp_mode.c"),
]);
@ -570,11 +569,11 @@ mod c {
}
if target.arch == "mips64" {
sources.extend(&[("__netf2", "comparetf2.c"), ("__fe_getround", "fp_mode.c")]);
sources.extend(&[("__fe_getround", "fp_mode.c")]);
}
if target.arch == "loongarch64" {
sources.extend(&[("__netf2", "comparetf2.c"), ("__fe_getround", "fp_mode.c")]);
sources.extend(&[("__fe_getround", "fp_mode.c")]);
}
// Remove the assembly implementations that won't compile for the target

View file

@ -1,13 +1,16 @@
#![cfg(not(feature = "no-asm"))]
// Interfaces used by naked trampolines.
extern "C" {
// SAFETY: these are defined in compiler-builtins
unsafe extern "C" {
fn __udivmodsi4(a: u32, b: u32, rem: *mut u32) -> u32;
fn __udivmoddi4(a: u64, b: u64, rem: *mut u64) -> u64;
fn __divmoddi4(a: i64, b: i64, rem: *mut i64) -> i64;
}
extern "aapcs" {
// SAFETY: these are defined in compiler-builtins
// FIXME(extern_custom), this isn't always the correct ABI
unsafe extern "aapcs" {
// AAPCS is not always the correct ABI for these intrinsics, but we only use this to
// forward another `__aeabi_` call so it doesn't matter.
fn __aeabi_idiv(a: i32, b: i32) -> i32;

View file

@ -1,5 +1,5 @@
use crate::float::Float;
use crate::int::{CastInto, Int, MinInt};
use crate::int::{CastFrom, CastInto, Int, MinInt};
/// Returns `a + b`
fn add<F: Float>(a: F, b: F) -> F
@ -12,7 +12,7 @@ where
let one = F::Int::ONE;
let zero = F::Int::ZERO;
let bits = F::BITS.cast();
let bits: F::Int = F::BITS.cast();
let significand_bits = F::SIG_BITS;
let max_exponent = F::EXP_SAT;
@ -115,9 +115,10 @@ where
let align = a_exponent.wrapping_sub(b_exponent).cast();
if align != MinInt::ZERO {
if align < bits {
let sticky =
F::Int::from_bool(b_significand << bits.wrapping_sub(align).cast() != MinInt::ZERO);
b_significand = (b_significand >> align.cast()) | sticky;
let sticky = F::Int::from_bool(
b_significand << u32::cast_from(bits.wrapping_sub(align)) != MinInt::ZERO,
);
b_significand = (b_significand >> u32::cast_from(align)) | sticky;
} else {
b_significand = one; // sticky; b is known to be non-zero.
}
@ -132,8 +133,8 @@ where
// If partial cancellation occured, we need to left-shift the result
// and adjust the exponent:
if a_significand < implicit_bit << 3 {
let shift =
a_significand.leading_zeros() as i32 - (implicit_bit << 3).leading_zeros() as i32;
let shift = a_significand.leading_zeros() as i32
- (implicit_bit << 3u32).leading_zeros() as i32;
a_significand <<= shift;
a_exponent -= shift;
}
@ -159,14 +160,15 @@ where
// Result is denormal before rounding; the exponent is zero and we
// need to shift the significand.
let shift = (1 - a_exponent).cast();
let sticky =
F::Int::from_bool((a_significand << bits.wrapping_sub(shift).cast()) != MinInt::ZERO);
a_significand = (a_significand >> shift.cast()) | sticky;
let sticky = F::Int::from_bool(
(a_significand << u32::cast_from(bits.wrapping_sub(shift))) != MinInt::ZERO,
);
a_significand = (a_significand >> u32::cast_from(shift)) | sticky;
a_exponent = 0;
}
// Low three bits are round, guard, and sticky.
let a_significand_i32: i32 = a_significand.cast();
let a_significand_i32: i32 = a_significand.cast_lossy();
let round_guard_sticky: i32 = a_significand_i32 & 0x7;
// Shift the significand into place, and mask off the implicit bit.

View file

@ -2,14 +2,23 @@
use crate::float::Float;
use crate::int::MinInt;
use crate::support::cfg_if;
// https://github.com/llvm/llvm-project/blob/1e6ba3cd2fe96be00b6ed6ba28b3d9f9271d784d/compiler-rt/lib/builtins/fp_compare_impl.inc#L22
#[cfg(target_arch = "avr")]
pub type CmpResult = i8;
// https://github.com/llvm/llvm-project/blob/1e6ba3cd2fe96be00b6ed6ba28b3d9f9271d784d/compiler-rt/lib/builtins/fp_compare_impl.inc#L25
#[cfg(not(target_arch = "avr"))]
pub type CmpResult = i32;
// Taken from LLVM config:
// https://github.com/llvm/llvm-project/blob/0cf3c437c18ed27d9663d87804a9a15ff6874af2/compiler-rt/lib/builtins/fp_compare_impl.inc#L11-L27
cfg_if! {
if #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))] {
// Aarch64 uses `int` rather than a pointer-sized value.
pub type CmpResult = i32;
} else if #[cfg(target_arch = "avr")] {
// AVR uses a single byte.
pub type CmpResult = i8;
} else {
// In compiler-rt, LLP64 ABIs use `long long` and everything else uses `long`. In effect,
// this means the return value is always pointer-sized.
pub type CmpResult = isize;
}
}
#[derive(Clone, Copy)]
enum Result {

View file

@ -72,9 +72,9 @@ mod int_to_float {
F: Float,
I: Int,
F::Int: CastFrom<I>,
Conv: Fn(I::UnsignedInt) -> F::Int,
Conv: Fn(I::Unsigned) -> F::Int,
{
let sign_bit = F::Int::cast_from(i >> (I::BITS - 1)) << (F::BITS - 1);
let sign_bit = F::Int::cast_from_lossy(i >> (I::BITS - 1)) << (F::BITS - 1);
F::from_bits(conv(i.unsigned_abs()) | sign_bit)
}
@ -166,7 +166,7 @@ mod int_to_float {
// Within the upper `F::BITS`, everything except for the signifcand
// gets truncated
let d1: u32 = (i_m >> (u128::BITS - f32::BITS - f32::SIG_BITS - 1)).cast();
let d1: u32 = (i_m >> (u128::BITS - f32::BITS - f32::SIG_BITS - 1)).cast_lossy();
// The entire rest of `i_m` gets truncated. Zero the upper `F::BITS` then just
// check if it is nonzero.
@ -313,10 +313,10 @@ intrinsics! {
fn float_to_unsigned_int<F, U>(f: F) -> U
where
F: Float,
U: Int<UnsignedInt = U>,
U: Int<Unsigned = U>,
F::Int: CastInto<U>,
F::Int: CastFrom<u32>,
F::Int: CastInto<U::UnsignedInt>,
F::Int: CastInto<U::Unsigned>,
u32: CastFrom<F::Int>,
{
float_to_int_inner::<F, U, _, _>(f.to_bits(), |i: U| i, || U::MAX)
@ -327,8 +327,8 @@ fn float_to_signed_int<F, I>(f: F) -> I
where
F: Float,
I: Int + Neg<Output = I>,
I::UnsignedInt: Int,
F::Int: CastInto<I::UnsignedInt>,
I::Unsigned: Int,
F::Int: CastInto<I::Unsigned>,
F::Int: CastFrom<u32>,
u32: CastFrom<F::Int>,
{
@ -355,27 +355,27 @@ where
I: Int,
FnFoo: FnOnce(I) -> I,
FnOob: FnOnce() -> I,
I::UnsignedInt: Int,
F::Int: CastInto<I::UnsignedInt>,
I::Unsigned: Int,
F::Int: CastInto<I::Unsigned>,
F::Int: CastFrom<u32>,
u32: CastFrom<F::Int>,
{
let int_max_exp = F::EXP_BIAS + I::MAX.ilog2() + 1;
let foobar = F::EXP_BIAS + I::UnsignedInt::BITS - 1;
let foobar = F::EXP_BIAS + I::Unsigned::BITS - 1;
if fbits < F::ONE.to_bits() {
// < 0 gets rounded to 0
I::ZERO
} else if fbits < F::Int::cast_from(int_max_exp) << F::SIG_BITS {
// >= 1, < integer max
let m_base = if I::UnsignedInt::BITS >= F::Int::BITS {
I::UnsignedInt::cast_from(fbits) << (I::BITS - F::SIG_BITS - 1)
let m_base = if I::Unsigned::BITS >= F::Int::BITS {
I::Unsigned::cast_from(fbits) << (I::BITS - F::SIG_BITS - 1)
} else {
I::UnsignedInt::cast_from(fbits >> (F::SIG_BITS - I::BITS + 1))
I::Unsigned::cast_from_lossy(fbits >> (F::SIG_BITS - I::BITS + 1))
};
// Set the implicit 1-bit.
let m: I::UnsignedInt = (I::UnsignedInt::ONE << (I::BITS - 1)) | m_base;
let m: I::Unsigned = (I::Unsigned::ONE << (I::BITS - 1)) | m_base;
// Shift based on the exponent and bias.
let s: u32 = (foobar) - u32::cast_from(fbits >> F::SIG_BITS);

View file

@ -370,7 +370,7 @@ where
let hi_corr: F::Int = corr_uq1 >> hw;
// x_UQ0 * corr_UQ1 = (x_UQ0_hw * 2^HW) * (hi_corr * 2^HW + lo_corr) - corr_UQ1
let mut x_uq0: F::Int = ((F::Int::from(x_uq0_hw) * hi_corr) << 1)
let mut x_uq0: F::Int = ((F::Int::from(x_uq0_hw) * hi_corr) << 1u32)
.wrapping_add((F::Int::from(x_uq0_hw) * lo_corr) >> (hw - 1))
// 1 to account for the highest bit of corr_UQ1 can be 1
// 1 to account for possible carry
@ -482,7 +482,7 @@ where
let ret = quotient.wrapping_shr(u32::cast_from(res_exponent.wrapping_neg()) + 1);
residual_lo = a_significand
.wrapping_shl(significand_bits.wrapping_add(CastInto::<u32>::cast(res_exponent)))
.wrapping_shl(significand_bits.wrapping_add(CastInto::<u32>::cast_lossy(res_exponent)))
.wrapping_sub(ret.wrapping_mul(b_significand) << 1);
ret
};

View file

@ -143,7 +143,7 @@ where
// a zero of the appropriate sign. Mathematically there is no need to
// handle this case separately, but we make it a special case to
// simplify the shift logic.
let shift = one.wrapping_sub(product_exponent.cast()).cast();
let shift: u32 = one.wrapping_sub(product_exponent.cast_lossy()).cast();
if shift >= bits {
return F::from_bits(product_sign);
}

View file

@ -32,8 +32,6 @@ intrinsics! {
#[ppc_alias = __powikf2]
#[cfg(f128_enabled)]
// FIXME(f16_f128): MSVC cannot build these until `__divtf3` is available in nightly.
#[cfg(not(target_env = "msvc"))]
pub extern "C" fn __powitf2(a: f128, b: i32) -> f128 {
pow(a, b)
}

View file

@ -20,10 +20,10 @@ pub trait Float:
+ ops::Rem<Output = Self>
{
/// A uint of the same width as the float
type Int: Int<OtherSign = Self::SignedInt, UnsignedInt = Self::Int>;
type Int: Int<OtherSign = Self::SignedInt, Unsigned = Self::Int>;
/// A int of the same width as the float
type SignedInt: Int + MinInt<OtherSign = Self::Int, UnsignedInt = Self::Int>;
type SignedInt: Int + MinInt<OtherSign = Self::Int, Unsigned = Self::Int>;
/// An int capable of containing the exponent bits plus a sign bit. This is signed.
type ExpInt: Int;

View file

@ -50,7 +50,7 @@ where
// The exponent of a is within the range of normal numbers in the
// destination format. We can convert by simply right-shifting with
// rounding and adjusting the exponent.
abs_result = (a_abs >> sig_bits_delta).cast();
abs_result = (a_abs >> sig_bits_delta).cast_lossy();
// Cast before shifting to prevent overflow.
let bias_diff: R::Int = src_exp_bias.wrapping_sub(dst_exp_bias).cast();
let tmp = bias_diff << R::SIG_BITS;

View file

@ -22,7 +22,7 @@ impl UAddSub for u128 {}
trait AddSub: Int
where
<Self as MinInt>::UnsignedInt: UAddSub,
<Self as MinInt>::Unsigned: UAddSub,
{
fn add(self, other: Self) -> Self {
Self::from_unsigned(self.unsigned().uadd(other.unsigned()))
@ -37,7 +37,7 @@ impl AddSub for i128 {}
trait Addo: AddSub
where
<Self as MinInt>::UnsignedInt: UAddSub,
<Self as MinInt>::Unsigned: UAddSub,
{
fn addo(self, other: Self) -> (Self, bool) {
let sum = AddSub::add(self, other);
@ -50,7 +50,7 @@ impl Addo for u128 {}
trait Subo: AddSub
where
<Self as MinInt>::UnsignedInt: UAddSub,
<Self as MinInt>::Unsigned: UAddSub,
{
fn subo(self, other: Self) -> (Self, bool) {
let sum = AddSub::sub(self, other);

View file

@ -45,7 +45,7 @@ impl i256 {
impl MinInt for u256 {
type OtherSign = i256;
type UnsignedInt = u256;
type Unsigned = u256;
const SIGNED: bool = false;
const BITS: u32 = 256;
@ -58,7 +58,7 @@ impl MinInt for u256 {
impl MinInt for i256 {
type OtherSign = u256;
type UnsignedInt = u256;
type Unsigned = u256;
const SIGNED: bool = false;
const BITS: u32 = 256;

View file

@ -9,11 +9,14 @@ pub use implementation::{leading_zeros_default, leading_zeros_riscv};
pub(crate) use implementation::{leading_zeros_default, leading_zeros_riscv};
mod implementation {
use crate::int::{CastInto, Int};
use crate::int::{CastFrom, Int};
/// Returns the number of leading binary zeros in `x`.
#[allow(dead_code)]
pub fn leading_zeros_default<T: Int + CastInto<usize>>(x: T) -> usize {
pub fn leading_zeros_default<I: Int>(x: I) -> usize
where
usize: CastFrom<I>,
{
// The basic idea is to test if the higher bits of `x` are zero and bisect the number
// of leading zeros. It is possible for all branches of the bisection to use the same
// code path by conditionally shifting the higher parts down to let the next bisection
@ -23,44 +26,48 @@ mod implementation {
// because it simplifies the final bisection step.
let mut x = x;
// the number of potential leading zeros
let mut z = T::BITS as usize;
let mut z = I::BITS as usize;
// a temporary
let mut t: T;
let mut t: I;
const { assert!(T::BITS <= 64) };
if T::BITS >= 64 {
const { assert!(I::BITS <= 64) };
if I::BITS >= 64 {
t = x >> 32;
if t != T::ZERO {
if t != I::ZERO {
z -= 32;
x = t;
}
}
if T::BITS >= 32 {
if I::BITS >= 32 {
t = x >> 16;
if t != T::ZERO {
if t != I::ZERO {
z -= 16;
x = t;
}
}
const { assert!(T::BITS >= 16) };
const { assert!(I::BITS >= 16) };
t = x >> 8;
if t != T::ZERO {
if t != I::ZERO {
z -= 8;
x = t;
}
t = x >> 4;
if t != T::ZERO {
if t != I::ZERO {
z -= 4;
x = t;
}
t = x >> 2;
if t != T::ZERO {
if t != I::ZERO {
z -= 2;
x = t;
}
// the last two bisections are combined into one conditional
t = x >> 1;
if t != T::ZERO { z - 2 } else { z - x.cast() }
if t != I::ZERO {
z - 2
} else {
z - usize::cast_from(x)
}
// We could potentially save a few cycles by using the LUT trick from
// "https://embeddedgurus.com/state-space/2014/09/
@ -82,10 +89,13 @@ mod implementation {
/// Returns the number of leading binary zeros in `x`.
#[allow(dead_code)]
pub fn leading_zeros_riscv<T: Int + CastInto<usize>>(x: T) -> usize {
pub fn leading_zeros_riscv<I: Int>(x: I) -> usize
where
usize: CastFrom<I>,
{
let mut x = x;
// the number of potential leading zeros
let mut z = T::BITS;
let mut z = I::BITS;
// a temporary
let mut t: u32;
@ -97,11 +107,11 @@ mod implementation {
// right). If we try to save an instruction by using `x < imm` for each bisection, we
// have to shift `x` left and compare with powers of two approaching `usize::MAX + 1`,
// but the immediate will never fit into 12 bits and never save an instruction.
const { assert!(T::BITS <= 64) };
if T::BITS >= 64 {
const { assert!(I::BITS <= 64) };
if I::BITS >= 64 {
// If the upper 32 bits of `x` are not all 0, `t` is set to `1 << 5`, otherwise
// `t` is set to 0.
t = ((x >= (T::ONE << 32)) as u32) << 5;
t = ((x >= (I::ONE << 32)) as u32) << 5;
// If `t` was set to `1 << 5`, then the upper 32 bits are shifted down for the
// next step to process.
x >>= t;
@ -109,27 +119,27 @@ mod implementation {
// leading zeros
z -= t;
}
if T::BITS >= 32 {
t = ((x >= (T::ONE << 16)) as u32) << 4;
if I::BITS >= 32 {
t = ((x >= (I::ONE << 16)) as u32) << 4;
x >>= t;
z -= t;
}
const { assert!(T::BITS >= 16) };
t = ((x >= (T::ONE << 8)) as u32) << 3;
const { assert!(I::BITS >= 16) };
t = ((x >= (I::ONE << 8)) as u32) << 3;
x >>= t;
z -= t;
t = ((x >= (T::ONE << 4)) as u32) << 2;
t = ((x >= (I::ONE << 4)) as u32) << 2;
x >>= t;
z -= t;
t = ((x >= (T::ONE << 2)) as u32) << 1;
t = ((x >= (I::ONE << 2)) as u32) << 1;
x >>= t;
z -= t;
t = (x >= (T::ONE << 1)) as u32;
t = (x >= (I::ONE << 1)) as u32;
x >>= t;
z -= t;
// All bits except the LSB are guaranteed to be zero for this final bisection step.
// If `x != 0` then `x == 1` and subtracts one potential zero from `z`.
z as usize - x.cast()
z as usize - usize::cast_from(x)
}
}

View file

@ -125,10 +125,10 @@ impl_normalization_shift!(
/// dependencies.
#[inline]
fn u64_by_u64_div_rem(duo: u64, div: u64) -> (u64, u64) {
if let Some(quo) = duo.checked_div(div) {
if let Some(rem) = duo.checked_rem(div) {
return (quo, rem);
}
if let Some(quo) = duo.checked_div(div)
&& let Some(rem) = duo.checked_rem(div)
{
return (quo, rem);
}
zero_div_fn()
}
@ -227,10 +227,10 @@ impl_asymmetric!(
#[inline]
#[allow(dead_code)]
fn u32_by_u32_div_rem(duo: u32, div: u32) -> (u32, u32) {
if let Some(quo) = duo.checked_div(div) {
if let Some(rem) = duo.checked_rem(div) {
return (quo, rem);
}
if let Some(quo) = duo.checked_div(div)
&& let Some(rem) = duo.checked_rem(div)
{
return (quo, rem);
}
zero_div_fn()
}

View file

@ -4,33 +4,38 @@ pub use implementation::trailing_zeros;
pub(crate) use implementation::trailing_zeros;
mod implementation {
use crate::int::{CastInto, Int};
use crate::int::{CastFrom, Int};
/// Returns number of trailing binary zeros in `x`.
#[allow(dead_code)]
pub fn trailing_zeros<T: Int + CastInto<u32> + CastInto<u16> + CastInto<u8>>(x: T) -> usize {
pub fn trailing_zeros<I: Int>(x: I) -> usize
where
u32: CastFrom<I>,
u16: CastFrom<I>,
u8: CastFrom<I>,
{
let mut x = x;
let mut r: u32 = 0;
let mut t: u32;
const { assert!(T::BITS <= 64) };
if T::BITS >= 64 {
r += ((CastInto::<u32>::cast(x) == 0) as u32) << 5; // if (x has no 32 small bits) t = 32 else 0
const { assert!(I::BITS <= 64) };
if I::BITS >= 64 {
r += ((u32::cast_from_lossy(x) == 0) as u32) << 5; // if (x has no 32 small bits) t = 32 else 0
x >>= r; // remove 32 zero bits
}
if T::BITS >= 32 {
t = ((CastInto::<u16>::cast(x) == 0) as u32) << 4; // if (x has no 16 small bits) t = 16 else 0
if I::BITS >= 32 {
t = ((u16::cast_from_lossy(x) == 0) as u32) << 4; // if (x has no 16 small bits) t = 16 else 0
r += t;
x >>= t; // x = [0 - 0xFFFF] + higher garbage bits
}
const { assert!(T::BITS >= 16) };
t = ((CastInto::<u8>::cast(x) == 0) as u32) << 3;
const { assert!(I::BITS >= 16) };
t = ((u8::cast_from_lossy(x) == 0) as u32) << 3;
x >>= t; // x = [0 - 0xFF] + higher garbage bits
r += t;
let mut x: u8 = x.cast();
let mut x: u8 = x.cast_lossy();
t = (((x & 0x0F) == 0) as u32) << 2;
x >>= t; // x = [0 - 0xF] + higher garbage bits

View file

@ -1,275 +1,4 @@
use core::ops;
/// Minimal integer implementations needed on all integer types, including wide integers.
#[allow(dead_code)]
pub trait MinInt:
Copy
+ core::fmt::Debug
+ ops::BitOr<Output = Self>
+ ops::Not<Output = Self>
+ ops::Shl<u32, Output = Self>
{
/// Type with the same width but other signedness
type OtherSign: MinInt;
/// Unsigned version of Self
type UnsignedInt: MinInt;
/// If `Self` is a signed integer
const SIGNED: bool;
/// The bitwidth of the int type
const BITS: u32;
const ZERO: Self;
const ONE: Self;
const MIN: Self;
const MAX: Self;
}
/// Trait for some basic operations on integers
#[allow(dead_code)]
pub trait Int:
MinInt
+ PartialEq
+ PartialOrd
+ ops::AddAssign
+ ops::SubAssign
+ ops::BitAndAssign
+ ops::BitOrAssign
+ ops::BitXorAssign
+ ops::ShlAssign<i32>
+ ops::ShrAssign<u32>
+ ops::Add<Output = Self>
+ ops::Sub<Output = Self>
+ ops::Mul<Output = Self>
+ ops::Div<Output = Self>
+ ops::Shr<u32, Output = Self>
+ ops::BitXor<Output = Self>
+ ops::BitAnd<Output = Self>
{
/// LUT used for maximizing the space covered and minimizing the computational cost of fuzzing
/// in `builtins-test`. For example, Self = u128 produces [0,1,2,7,8,15,16,31,32,63,64,95,96,
/// 111,112,119,120,125,126,127].
const FUZZ_LENGTHS: [u8; 20] = make_fuzz_lengths(<Self as MinInt>::BITS);
/// The number of entries of `FUZZ_LENGTHS` actually used. The maximum is 20 for u128.
const FUZZ_NUM: usize = {
let log2 = (<Self as MinInt>::BITS - 1).count_ones() as usize;
if log2 == 3 {
// case for u8
6
} else {
// 3 entries on each extreme, 2 in the middle, and 4 for each scale of intermediate
// boundaries.
8 + (4 * (log2 - 4))
}
};
fn unsigned(self) -> Self::UnsignedInt;
fn from_unsigned(unsigned: Self::UnsignedInt) -> Self;
fn unsigned_abs(self) -> Self::UnsignedInt;
fn from_bool(b: bool) -> Self;
/// Prevents the need for excessive conversions between signed and unsigned
fn logical_shr(self, other: u32) -> Self;
/// Absolute difference between two integers.
fn abs_diff(self, other: Self) -> Self::UnsignedInt;
// copied from primitive integers, but put in a trait
fn is_zero(self) -> bool;
fn wrapping_neg(self) -> Self;
fn wrapping_add(self, other: Self) -> Self;
fn wrapping_mul(self, other: Self) -> Self;
fn wrapping_sub(self, other: Self) -> Self;
fn wrapping_shl(self, other: u32) -> Self;
fn wrapping_shr(self, other: u32) -> Self;
fn rotate_left(self, other: u32) -> Self;
fn overflowing_add(self, other: Self) -> (Self, bool);
fn leading_zeros(self) -> u32;
fn ilog2(self) -> u32;
}
pub(crate) const fn make_fuzz_lengths(bits: u32) -> [u8; 20] {
let mut v = [0u8; 20];
v[0] = 0;
v[1] = 1;
v[2] = 2; // important for parity and the iX::MIN case when reversed
let mut i = 3;
// No need for any more until the byte boundary, because there should be no algorithms
// that are sensitive to anything not next to byte boundaries after 2. We also scale
// in powers of two, which is important to prevent u128 corner tests from getting too
// big.
let mut l = 8;
loop {
if l >= ((bits / 2) as u8) {
break;
}
// get both sides of the byte boundary
v[i] = l - 1;
i += 1;
v[i] = l;
i += 1;
l *= 2;
}
if bits != 8 {
// add the lower side of the middle boundary
v[i] = ((bits / 2) - 1) as u8;
i += 1;
}
// We do not want to jump directly from the Self::BITS/2 boundary to the Self::BITS
// boundary because of algorithms that split the high part up. We reverse the scaling
// as we go to Self::BITS.
let mid = i;
let mut j = 1;
loop {
v[i] = (bits as u8) - (v[mid - j]) - 1;
if j == mid {
break;
}
i += 1;
j += 1;
}
v
}
macro_rules! int_impl_common {
($ty:ty) => {
fn from_bool(b: bool) -> Self {
b as $ty
}
fn logical_shr(self, other: u32) -> Self {
Self::from_unsigned(self.unsigned().wrapping_shr(other))
}
fn is_zero(self) -> bool {
self == Self::ZERO
}
fn wrapping_neg(self) -> Self {
<Self>::wrapping_neg(self)
}
fn wrapping_add(self, other: Self) -> Self {
<Self>::wrapping_add(self, other)
}
fn wrapping_mul(self, other: Self) -> Self {
<Self>::wrapping_mul(self, other)
}
fn wrapping_sub(self, other: Self) -> Self {
<Self>::wrapping_sub(self, other)
}
fn wrapping_shl(self, other: u32) -> Self {
<Self>::wrapping_shl(self, other)
}
fn wrapping_shr(self, other: u32) -> Self {
<Self>::wrapping_shr(self, other)
}
fn rotate_left(self, other: u32) -> Self {
<Self>::rotate_left(self, other)
}
fn overflowing_add(self, other: Self) -> (Self, bool) {
<Self>::overflowing_add(self, other)
}
fn leading_zeros(self) -> u32 {
<Self>::leading_zeros(self)
}
fn ilog2(self) -> u32 {
<Self>::ilog2(self)
}
};
}
macro_rules! int_impl {
($ity:ty, $uty:ty) => {
impl MinInt for $uty {
type OtherSign = $ity;
type UnsignedInt = $uty;
const BITS: u32 = <Self as MinInt>::ZERO.count_zeros();
const SIGNED: bool = Self::MIN != Self::ZERO;
const ZERO: Self = 0;
const ONE: Self = 1;
const MIN: Self = <Self>::MIN;
const MAX: Self = <Self>::MAX;
}
impl Int for $uty {
fn unsigned(self) -> $uty {
self
}
// It makes writing macros easier if this is implemented for both signed and unsigned
#[allow(clippy::wrong_self_convention)]
fn from_unsigned(me: $uty) -> Self {
me
}
fn unsigned_abs(self) -> Self {
self
}
fn abs_diff(self, other: Self) -> Self {
self.abs_diff(other)
}
int_impl_common!($uty);
}
impl MinInt for $ity {
type OtherSign = $uty;
type UnsignedInt = $uty;
const BITS: u32 = <Self as MinInt>::ZERO.count_zeros();
const SIGNED: bool = Self::MIN != Self::ZERO;
const ZERO: Self = 0;
const ONE: Self = 1;
const MIN: Self = <Self>::MIN;
const MAX: Self = <Self>::MAX;
}
impl Int for $ity {
fn unsigned(self) -> $uty {
self as $uty
}
fn from_unsigned(me: $uty) -> Self {
me as $ity
}
fn unsigned_abs(self) -> Self::UnsignedInt {
self.unsigned_abs()
}
fn abs_diff(self, other: Self) -> $uty {
self.abs_diff(other)
}
int_impl_common!($ity);
}
};
}
int_impl!(isize, usize);
int_impl!(i8, u8);
int_impl!(i16, u16);
int_impl!(i32, u32);
int_impl!(i64, u64);
int_impl!(i128, u128);
pub use crate::support::{CastFrom, CastInto, Int, MinInt};
/// Trait for integers twice the bit width of another integer. This is implemented for all
/// primitives except for `u8`, because there is not a smaller primitive.
@ -368,44 +97,3 @@ impl_h_int!(
i32 u32 i64,
i64 u64 i128
);
/// Trait to express (possibly lossy) casting of integers
pub trait CastInto<T: Copy>: Copy {
fn cast(self) -> T;
}
pub trait CastFrom<T: Copy>: Copy {
fn cast_from(value: T) -> Self;
}
impl<T: Copy, U: CastInto<T> + Copy> CastFrom<U> for T {
fn cast_from(value: U) -> Self {
value.cast()
}
}
macro_rules! cast_into {
($ty:ty) => {
cast_into!($ty; usize, isize, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128);
};
($ty:ty; $($into:ty),*) => {$(
impl CastInto<$into> for $ty {
fn cast(self) -> $into {
self as $into
}
}
)*};
}
cast_into!(usize);
cast_into!(isize);
cast_into!(u8);
cast_into!(i8);
cast_into!(u16);
cast_into!(i16);
cast_into!(u32);
cast_into!(i32);
cast_into!(u64);
cast_into!(i64);
cast_into!(u128);
cast_into!(i128);

View file

@ -132,7 +132,7 @@ macro_rules! intrinsics {
) => (
#[cfg($name = "optimized-c")]
pub $(unsafe $($empty)? )? extern $abi fn $name( $($argname: $ty),* ) $(-> $ret)? {
extern $abi {
unsafe extern $abi {
fn $name($($argname: $ty),*) $(-> $ret)?;
}
unsafe {
@ -435,7 +435,7 @@ macro_rules! intrinsics {
pub mod $name {
#[unsafe(naked)]
$(#[$($attr)*])*
#[cfg_attr(not(feature = "mangled-names"), no_mangle)]
#[cfg_attr(not(feature = "mangled-names"), unsafe(no_mangle))]
#[cfg_attr(not(any(all(windows, target_env = "gnu"), target_os = "cygwin")), linkage = "weak")]
pub unsafe extern $abi fn $name( $($argname: $ty),* ) $(-> $ret)? {
$($body)*

View file

@ -49,7 +49,9 @@
// We only define stack probing for these architectures today.
#![cfg(any(target_arch = "x86_64", target_arch = "x86"))]
extern "C" {
// SAFETY: defined in this module.
// FIXME(extern_custom): the ABI is not correct.
unsafe extern "C" {
pub fn __rust_probestack();
}

View file

@ -0,0 +1,7 @@
[package]
name = "josh-sync"
edition = "2024"
publish = false
[dependencies]
directories = "6.0.0"

View file

@ -0,0 +1,45 @@
use std::io::{Read, Write};
use std::process::exit;
use std::{env, io};
use crate::sync::{GitSync, Josh};
mod sync;
const USAGE: &str = r#"Utility for synchroniing compiler-builtins with rust-lang/rust
Usage:
josh-sync rustc-pull
Pull from rust-lang/rust to compiler-builtins. Creates a commit
updating the version file, followed by a merge commit.
josh-sync rustc-push GITHUB_USERNAME [BRANCH]
Create a branch off of rust-lang/rust updating compiler-builtins.
"#;
fn main() {
let sync = GitSync::from_current_dir();
// Collect args, then recollect as str refs so we can match on them
let args: Vec<_> = env::args().collect();
let args: Vec<&str> = args.iter().map(String::as_str).collect();
match args.as_slice()[1..] {
["rustc-pull"] => sync.rustc_pull(None),
["rustc-push", github_user, branch] => sync.rustc_push(github_user, Some(branch)),
["rustc-push", github_user] => sync.rustc_push(github_user, None),
["start-josh"] => {
let _josh = Josh::start();
println!("press enter to stop");
io::stdout().flush().unwrap();
let _ = io::stdin().read(&mut [0u8]).unwrap();
}
_ => {
println!("{USAGE}");
exit(1);
}
}
}

View file

@ -0,0 +1,371 @@
use std::net::{SocketAddr, TcpStream};
use std::process::{Command, Stdio, exit};
use std::time::Duration;
use std::{env, fs, process, thread};
const JOSH_PORT: u16 = 42042;
const DEFAULT_PR_BRANCH: &str = "update-builtins";
pub struct GitSync {
upstream_repo: String,
upstream_ref: String,
upstream_url: String,
josh_filter: String,
josh_url_base: String,
}
/// This code was adapted from the miri repository, via the rustc-dev-guide
/// (<https://github.com/rust-lang/rustc-dev-guide/tree/c51adbd12d/josh-sync>)
impl GitSync {
pub fn from_current_dir() -> Self {
let upstream_repo =
env::var("UPSTREAM_ORG").unwrap_or_else(|_| "rust-lang".to_owned()) + "/rust";
Self {
upstream_url: format!("https://github.com/{upstream_repo}"),
upstream_repo,
upstream_ref: env::var("UPSTREAM_REF").unwrap_or_else(|_| "HEAD".to_owned()),
josh_filter: ":/library/compiler-builtins".to_owned(),
josh_url_base: format!("http://localhost:{JOSH_PORT}"),
}
}
/// Pull from rust-lang/rust to compiler-builtins.
pub fn rustc_pull(&self, commit: Option<String>) {
let Self {
upstream_ref,
upstream_url,
upstream_repo,
..
} = self;
let new_upstream_base = commit.unwrap_or_else(|| {
let out = check_output(["git", "ls-remote", upstream_url, upstream_ref]);
out.split_whitespace()
.next()
.unwrap_or_else(|| panic!("could not split output: '{out}'"))
.to_owned()
});
ensure_clean();
// Make sure josh is running.
let _josh = Josh::start();
let josh_url_filtered = self.josh_url(
&self.upstream_repo,
Some(&new_upstream_base),
Some(&self.josh_filter),
);
let previous_upstream_base = fs::read_to_string("rust-version")
.expect("failed to read `rust-version`")
.trim()
.to_string();
assert_ne!(previous_upstream_base, new_upstream_base, "nothing to pull");
let orig_head = check_output(["git", "rev-parse", "HEAD"]);
println!("original upstream base: {previous_upstream_base}");
println!("new upstream base: {new_upstream_base}");
println!("original HEAD: {orig_head}");
// Fetch the latest upstream HEAD so we can get a summary. Use the Josh URL for caching.
run([
"git",
"fetch",
&self.josh_url(&self.upstream_repo, Some(&new_upstream_base), Some(":/")),
&new_upstream_base,
"--depth=1",
]);
let new_summary = check_output(["git", "log", "-1", "--format=%h %s", &new_upstream_base]);
// Update rust-version file. As a separate commit, since making it part of
// the merge has confused the heck out of josh in the past.
// We pass `--no-verify` to avoid running git hooks.
// We do this before the merge so that if there are merge conflicts, we have
// the right rust-version file while resolving them.
fs::write("rust-version", format!("{new_upstream_base}\n"))
.expect("failed to write rust-version");
let prep_message = format!(
"Update the upstream Rust version\n\n\
To prepare for merging from {upstream_repo}, set the version file to:\n\n \
{new_summary}\n\
",
);
run([
"git",
"commit",
"rust-version",
"--no-verify",
"-m",
&prep_message,
]);
// Fetch given rustc commit.
run(["git", "fetch", &josh_url_filtered]);
let incoming_ref = check_output(["git", "rev-parse", "FETCH_HEAD"]);
println!("incoming ref: {incoming_ref}");
let merge_message = format!(
"Merge ref '{upstream_head_short}{filter}' from {upstream_url}\n\n\
Pull recent changes from {upstream_repo} via Josh.\n\n\
Upstream ref: {new_upstream_base}\n\
Filtered ref: {incoming_ref}\n\
",
upstream_head_short = &new_upstream_base[..12],
filter = self.josh_filter
);
// This should not add any new root commits. So count those before and after merging.
let num_roots = || -> u32 {
let out = check_output(["git", "rev-list", "HEAD", "--max-parents=0", "--count"]);
out.trim()
.parse::<u32>()
.unwrap_or_else(|e| panic!("failed to parse `{out}`: {e}"))
};
let num_roots_before = num_roots();
let pre_merge_sha = check_output(["git", "rev-parse", "HEAD"]);
println!("pre-merge HEAD: {pre_merge_sha}");
// Merge the fetched commit.
run([
"git",
"merge",
"FETCH_HEAD",
"--no-verify",
"--no-ff",
"-m",
&merge_message,
]);
let current_sha = check_output(["git", "rev-parse", "HEAD"]);
if current_sha == pre_merge_sha {
run(["git", "reset", "--hard", &orig_head]);
eprintln!(
"No merge was performed, no changes to pull were found. \
Rolled back the preparation commit."
);
exit(1);
}
// Check that the number of roots did not increase.
assert_eq!(
num_roots(),
num_roots_before,
"Josh created a new root commit. This is probably not the history you want."
);
}
/// Construct an update to rust-lang/rust from compiler-builtins.
pub fn rustc_push(&self, github_user: &str, branch: Option<&str>) {
let Self {
josh_filter,
upstream_url,
..
} = self;
let branch = branch.unwrap_or(DEFAULT_PR_BRANCH);
let josh_url = self.josh_url(&format!("{github_user}/rust"), None, Some(josh_filter));
let user_upstream_url = format!("git@github.com:{github_user}/rust.git");
let Ok(rustc_git) = env::var("RUSTC_GIT") else {
panic!("the RUSTC_GIT environment variable must be set to a rust-lang/rust checkout")
};
ensure_clean();
let base = fs::read_to_string("rust-version")
.expect("failed to read `rust-version`")
.trim()
.to_string();
// Make sure josh is running.
let _josh = Josh::start();
// Prepare the branch. Pushing works much better if we use as base exactly
// the commit that we pulled from last time, so we use the `rust-version`
// file to find out which commit that would be.
println!("Preparing {github_user}/rust (base: {base})...");
if Command::new("git")
.args(["-C", &rustc_git, "fetch", &user_upstream_url, branch])
.output() // capture output
.expect("could not run fetch")
.status
.success()
{
panic!(
"The branch '{branch}' seems to already exist in '{user_upstream_url}'. \
Please delete it and try again."
);
}
run(["git", "-C", &rustc_git, "fetch", upstream_url, &base]);
run_cfg("git", |c| {
c.args([
"-C",
&rustc_git,
"push",
&user_upstream_url,
&format!("{base}:refs/heads/{branch}"),
])
.stdout(Stdio::null())
.stderr(Stdio::null()) // silence the "create GitHub PR" message
});
println!("pushed PR branch");
// Do the actual push.
println!("Pushing changes...");
run(["git", "push", &josh_url, &format!("HEAD:{branch}")]);
println!();
// Do a round-trip check to make sure the push worked as expected.
run(["git", "fetch", &josh_url, branch]);
let head = check_output(["git", "rev-parse", "HEAD"]);
let fetch_head = check_output(["git", "rev-parse", "FETCH_HEAD"]);
assert_eq!(
head, fetch_head,
"Josh created a non-roundtrip push! Do NOT merge this into rustc!\n\
Expected {head}, got {fetch_head}."
);
println!(
"Confirmed that the push round-trips back to compiler-builtins properly. Please \
create a rustc PR:"
);
// Open PR with `subtree update` title to silence the `no-merges` triagebot check
println!(
" {upstream_url}/compare/{github_user}:{branch}?quick_pull=1\
&title=Update%20the%20%60compiler-builtins%60%20subtree\
&body=Update%20the%20Josh%20subtree%20to%20https%3A%2F%2Fgithub.com%2Frust-lang%2F\
compiler-builtins%2Fcommit%2F{head_short}.%0A%0Ar%3F%20%40ghost",
head_short = &head[..12],
);
}
/// Construct a url to the local Josh server with (optionally)
fn josh_url(&self, repo: &str, rev: Option<&str>, filter: Option<&str>) -> String {
format!(
"{base}/{repo}.git{at}{rev}{filter}{filt_git}",
base = self.josh_url_base,
at = if rev.is_some() { "@" } else { "" },
rev = rev.unwrap_or_default(),
filter = filter.unwrap_or_default(),
filt_git = if filter.is_some() { ".git" } else { "" }
)
}
}
/// Fail if there are files that need to be checked in.
fn ensure_clean() {
let read = check_output(["git", "status", "--untracked-files=no", "--porcelain"]);
assert!(
read.is_empty(),
"working directory must be clean before performing rustc pull"
);
}
/* Helpers for running commands with logged invocations */
/// Run a command from an array, passing its output through.
fn run<'a, Args: AsRef<[&'a str]>>(l: Args) {
let l = l.as_ref();
run_cfg(l[0], |c| c.args(&l[1..]));
}
/// Run a command from an array, collecting its output.
fn check_output<'a, Args: AsRef<[&'a str]>>(l: Args) -> String {
let l = l.as_ref();
check_output_cfg(l[0], |c| c.args(&l[1..]))
}
/// [`run`] with configuration.
fn run_cfg(prog: &str, f: impl FnOnce(&mut Command) -> &mut Command) {
// self.read(l.as_ref());
check_output_cfg(prog, |c| f(c.stdout(Stdio::inherit())));
}
/// [`read`] with configuration. All shell helpers print the command and pass stderr.
fn check_output_cfg(prog: &str, f: impl FnOnce(&mut Command) -> &mut Command) -> String {
let mut cmd = Command::new(prog);
cmd.stderr(Stdio::inherit());
f(&mut cmd);
eprintln!("+ {cmd:?}");
let out = cmd.output().expect("command failed");
assert!(out.status.success());
String::from_utf8(out.stdout.trim_ascii().to_vec()).expect("non-UTF8 output")
}
/// Create a wrapper that stops Josh on drop.
pub struct Josh(process::Child);
impl Josh {
pub fn start() -> Self {
// Determine cache directory.
let user_dirs =
directories::ProjectDirs::from("org", "rust-lang", "rustc-compiler-builtins-josh")
.unwrap();
let local_dir = user_dirs.cache_dir().to_owned();
// Start josh, silencing its output.
#[expect(clippy::zombie_processes, reason = "clippy can't handle the loop")]
let josh = process::Command::new("josh-proxy")
.arg("--local")
.arg(local_dir)
.args([
"--remote=https://github.com",
&format!("--port={JOSH_PORT}"),
"--no-background",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("failed to start josh-proxy, make sure it is installed");
// Wait until the port is open. We try every 10ms until 1s passed.
for _ in 0..100 {
// This will generally fail immediately when the port is still closed.
let addr = SocketAddr::from(([127, 0, 0, 1], JOSH_PORT));
let josh_ready = TcpStream::connect_timeout(&addr, Duration::from_millis(1));
if josh_ready.is_ok() {
println!("josh up and running");
return Josh(josh);
}
// Not ready yet.
thread::sleep(Duration::from_millis(10));
}
panic!("Even after waiting for 1s, josh-proxy is still not available.")
}
}
impl Drop for Josh {
fn drop(&mut self) {
if cfg!(unix) {
// Try to gracefully shut it down.
Command::new("kill")
.args(["-s", "INT", &self.0.id().to_string()])
.output()
.expect("failed to SIGINT josh-proxy");
// Sadly there is no "wait with timeout"... so we just give it some time to finish.
thread::sleep(Duration::from_millis(100));
// Now hopefully it is gone.
if self
.0
.try_wait()
.expect("failed to wait for josh-proxy")
.is_some()
{
return;
}
}
// If that didn't work (or we're not on Unix), kill it hard.
eprintln!(
"I have to kill josh-proxy the hard way, let's hope this does not \
break anything."
);
self.0.kill().expect("failed to SIGKILL josh-proxy");
}
}

View file

@ -10,9 +10,9 @@ proc-macro = true
[dependencies]
heck = "0.5.0"
proc-macro2 = "1.0.94"
proc-macro2 = "1.0.95"
quote = "1.0.40"
syn = { version = "2.0.100", features = ["full", "extra-traits", "visit-mut"] }
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
[lints.rust]
# Values used during testing

View file

@ -11,4 +11,4 @@ license = "MIT OR Apache-2.0"
libm = { path = "../../libm" }
[build-dependencies]
cc = "1.2.16"
cc = "1.2.25"

View file

@ -120,7 +120,7 @@ fn build_musl_math(cfg: &Config) {
let arch_dir = musl_dir.join("arch").join(&cfg.musl_arch);
assert!(
math.exists(),
"musl source not found. Is the submodule up to date?"
"musl source not found. You may need to run `./ci/update-musl.sh`."
);
let source_map = find_math_source(&math, cfg);

@ -1 +0,0 @@
Subproject commit c47ad25ea3b484e10326f933e927c0bc8cded3da

View file

@ -1,11 +1,8 @@
//! This is needed for tests on targets that require a `#[panic_handler]` function
#![feature(no_core)]
#![no_core]
extern crate core;
#![no_std]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
fn panic(_: &core::panic::PanicInfo<'_>) -> ! {
loop {}
}

View file

@ -0,0 +1,13 @@
[package]
name = "symbol-check"
version = "0.1.0"
edition = "2024"
publish = false
[dependencies]
# FIXME: used as a git dependency since the latest release does not support wasm
object = { git = "https://github.com/gimli-rs/object.git", rev = "013fac75da56a684377af4151b8164b78c1790e0" }
serde_json = "1.0.140"
[features]
wasm = ["object/wasm"]

View file

@ -0,0 +1,232 @@
//! Tool used by CI to inspect compiler-builtins archives and help ensure we won't run into any
//! linking errors.
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use object::read::archive::{ArchiveFile, ArchiveMember};
use object::{Object, ObjectSymbol, Symbol, SymbolKind, SymbolScope, SymbolSection};
use serde_json::Value;
const CHECK_LIBRARIES: &[&str] = &["compiler_builtins", "builtins_test_intrinsics"];
const CHECK_EXTENSIONS: &[Option<&str>] = &[Some("rlib"), Some("a"), Some("exe"), None];
const USAGE: &str = "Usage:
symbol-check build-and-check CARGO_ARGS ...
Cargo will get invoked with `CARGO_ARGS` and all output
`compiler_builtins*.rlib` files will be checked.
";
fn main() {
// Create a `&str` vec so we can match on it.
let args = std::env::args().collect::<Vec<_>>();
let args_ref = args.iter().map(String::as_str).collect::<Vec<_>>();
match &args_ref[1..] {
["build-and-check", rest @ ..] if !rest.is_empty() => {
let paths = exec_cargo_with_args(rest);
for path in paths {
println!("Checking {}", path.display());
verify_no_duplicates(&path);
verify_core_symbols(&path);
}
}
_ => {
println!("{USAGE}");
std::process::exit(1);
}
}
}
/// Run `cargo build` with the provided additional arguments, collecting the list of created
/// libraries.
fn exec_cargo_with_args(args: &[&str]) -> Vec<PathBuf> {
let mut cmd = Command::new("cargo");
cmd.arg("build")
.arg("--message-format=json")
.args(args)
.stdout(Stdio::piped());
println!("running: {cmd:?}");
let mut child = cmd.spawn().expect("failed to launch Cargo");
let stdout = child.stdout.take().unwrap();
let reader = BufReader::new(stdout);
let mut check_files = Vec::new();
for line in reader.lines() {
let line = line.expect("failed to read line");
println!("{line}"); // tee to stdout
// Select only steps that create files
let j: Value = serde_json::from_str(&line).expect("failed to deserialize");
if j["reason"] != "compiler-artifact" {
continue;
}
// Find rlibs in the created file list that match our expected library names and
// extensions.
for fpath in j["filenames"].as_array().expect("filenames not an array") {
let path = fpath.as_str().expect("file name not a string");
let path = PathBuf::from(path);
if CHECK_EXTENSIONS.contains(&path.extension().map(|ex| ex.to_str().unwrap())) {
let fname = path.file_name().unwrap().to_str().unwrap();
if CHECK_LIBRARIES.iter().any(|lib| fname.contains(lib)) {
check_files.push(path);
}
}
}
}
assert!(child.wait().expect("failed to wait on Cargo").success());
assert!(!check_files.is_empty(), "no compiler_builtins rlibs found");
println!("Collected the following rlibs to check: {check_files:#?}");
check_files
}
/// Information collected from `object`, for convenience.
#[expect(unused)] // only for printing
#[derive(Clone, Debug)]
struct SymInfo {
name: String,
kind: SymbolKind,
scope: SymbolScope,
section: SymbolSection,
is_undefined: bool,
is_global: bool,
is_local: bool,
is_weak: bool,
is_common: bool,
address: u64,
object: String,
}
impl SymInfo {
fn new(sym: &Symbol, member: &ArchiveMember) -> Self {
Self {
name: sym.name().expect("missing name").to_owned(),
kind: sym.kind(),
scope: sym.scope(),
section: sym.section(),
is_undefined: sym.is_undefined(),
is_global: sym.is_global(),
is_local: sym.is_local(),
is_weak: sym.is_weak(),
is_common: sym.is_common(),
address: sym.address(),
object: String::from_utf8_lossy(member.name()).into_owned(),
}
}
}
/// Ensure that the same global symbol isn't defined in multiple object files within an archive.
///
/// Note that this will also locate cases where a symbol is weakly defined in more than one place.
/// Technically there are no linker errors that will come from this, but it keeps our binary more
/// straightforward and saves some distribution size.
fn verify_no_duplicates(path: &Path) {
let mut syms = BTreeMap::<String, SymInfo>::new();
let mut dups = Vec::new();
let mut found_any = false;
for_each_symbol(path, |symbol, member| {
// Only check defined globals
if !symbol.is_global() || symbol.is_undefined() {
return;
}
let sym = SymInfo::new(&symbol, member);
// x86-32 includes multiple copies of thunk symbols
if sym.name.starts_with("__x86.get_pc_thunk") {
return;
}
// Windows has symbols for literal numeric constants, string literals, and MinGW pseudo-
// relocations. These are allowed to have repeated definitions.
let win_allowed_dup_pfx = ["__real@", "__xmm@", "??_C@_", ".refptr"];
if win_allowed_dup_pfx
.iter()
.any(|pfx| sym.name.starts_with(pfx))
{
return;
}
match syms.get(&sym.name) {
Some(existing) => {
dups.push(sym);
dups.push(existing.clone());
}
None => {
syms.insert(sym.name.clone(), sym);
}
}
found_any = true;
});
assert!(found_any, "no symbols found");
if !dups.is_empty() {
dups.sort_unstable_by(|a, b| a.name.cmp(&b.name));
panic!("found duplicate symbols: {dups:#?}");
}
println!(" success: no duplicate symbols found");
}
/// Ensure that there are no references to symbols from `core` that aren't also (somehow) defined.
fn verify_core_symbols(path: &Path) {
let mut defined = BTreeSet::new();
let mut undefined = Vec::new();
let mut has_symbols = false;
for_each_symbol(path, |symbol, member| {
has_symbols = true;
// Find only symbols from `core`
if !symbol.name().unwrap().contains("_ZN4core") {
return;
}
let sym = SymInfo::new(&symbol, member);
if sym.is_undefined {
undefined.push(sym);
} else {
defined.insert(sym.name);
}
});
assert!(has_symbols, "no symbols found");
// Discard any symbols that are defined somewhere in the archive
undefined.retain(|sym| !defined.contains(&sym.name));
if !undefined.is_empty() {
undefined.sort_unstable_by(|a, b| a.name.cmp(&b.name));
panic!("found undefined symbols from core: {undefined:#?}");
}
println!(" success: no undefined references to core found");
}
/// For a given archive path, do something with each symbol.
fn for_each_symbol(path: &Path, mut f: impl FnMut(Symbol, &ArchiveMember)) {
let data = fs::read(path).expect("reading file failed");
let archive = ArchiveFile::parse(data.as_slice()).expect("archive parse failed");
for member in archive.members() {
let member = member.expect("failed to access member");
let obj_data = member.data(&*data).expect("failed to access object");
let obj = object::File::parse(obj_data).expect("failed to parse object");
obj.symbols().for_each(|sym| f(sym, &member));
}
}

View file

@ -6,7 +6,7 @@ publish = false
license = "MIT OR Apache-2.0"
[features]
default = ["build-mpfr", "build-musl", "unstable-float"]
default = ["build-mpfr", "unstable-float"]
# Propagated from libm because this affects which functions we test.
unstable-float = ["libm/unstable-float", "rug?/nightly-float"]
@ -28,28 +28,28 @@ icount = ["dep:iai-callgrind"]
short-benchmarks = []
[dependencies]
anyhow = "1.0.97"
anyhow = "1.0.98"
# This is not directly used but is required so we can enable `gmp-mpfr-sys/force-cross`.
gmp-mpfr-sys = { version = "1.6.4", optional = true, default-features = false }
iai-callgrind = { version = "0.14.0", optional = true }
gmp-mpfr-sys = { version = "1.6.5", optional = true, default-features = false }
iai-callgrind = { version = "0.14.1", optional = true }
indicatif = { version = "0.17.11", default-features = false }
libm = { path = "../libm", features = ["unstable-public-internals"] }
libm-macros = { path = "../crates/libm-macros" }
musl-math-sys = { path = "../crates/musl-math-sys", optional = true }
paste = "1.0.15"
rand = "0.9.0"
rand = "0.9.1"
rand_chacha = "0.9.0"
rayon = "1.10.0"
rug = { version = "1.27.0", optional = true, default-features = false, features = ["float", "integer", "std"] }
[target.'cfg(target_family = "wasm")'.dependencies]
getrandom = { version = "0.3.2", features = ["wasm_js"] }
getrandom = { version = "0.3.3", features = ["wasm_js"] }
[build-dependencies]
rand = { version = "0.9.0", optional = true }
rand = { version = "0.9.1", optional = true }
[dev-dependencies]
criterion = { version = "0.5.1", default-features = false, features = ["cargo_bench_support"] }
criterion = { version = "0.6.0", default-features = false, features = ["cargo_bench_support"] }
libtest-mimic = "0.8.1"
[[bench]]

View file

@ -1,9 +1,11 @@
//! Benchmarks that use `iai-cachegrind` to be reasonably CI-stable.
#![feature(f16)]
#![feature(f128)]
use std::hint::black_box;
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
use libm::support::{HInt, u256};
use libm::support::{HInt, Hexf, hf16, hf32, hf64, hf128, u256};
use libm_test::generate::spaced;
use libm_test::{CheckBasis, CheckCtx, GeneratorKind, MathOp, OpRustArgs, TupleCall, op};
@ -21,7 +23,7 @@ macro_rules! icount_benches {
let mut ctx = CheckCtx::new(
Op::IDENTIFIER,
CheckBasis::None,
GeneratorKind::QuickSpaced
GeneratorKind::Spaced
);
ctx.override_iterations(BENCH_ITER_ITEMS);
let ret = spaced::get_test_cases::<Op>(&ctx).0.collect::<Vec<_>>();
@ -109,11 +111,6 @@ fn icount_bench_u128_widen_mul(cases: Vec<(u128, u128)>) {
}
}
library_benchmark_group!(
name = icount_bench_u128_widen_mul_group;
benchmarks = icount_bench_u128_widen_mul
);
#[library_benchmark]
#[bench::linspace(setup_u256_add())]
fn icount_bench_u256_add(cases: Vec<(u256, u256)>) {
@ -122,11 +119,6 @@ fn icount_bench_u256_add(cases: Vec<(u256, u256)>) {
}
}
library_benchmark_group!(
name = icount_bench_u256_add_group;
benchmarks = icount_bench_u256_add
);
#[library_benchmark]
#[bench::linspace(setup_u256_shift())]
fn icount_bench_u256_shr(cases: Vec<(u256, u32)>) {
@ -136,16 +128,90 @@ fn icount_bench_u256_shr(cases: Vec<(u256, u32)>) {
}
library_benchmark_group!(
name = icount_bench_u256_shr_group;
benchmarks = icount_bench_u256_shr
name = icount_bench_u128_group;
benchmarks = icount_bench_u128_widen_mul, icount_bench_u256_add, icount_bench_u256_shr
);
#[library_benchmark]
#[bench::short("0x12.34p+8")]
#[bench::max("0x1.ffcp+15")]
fn icount_bench_hf16(s: &str) -> f16 {
black_box(hf16(s))
}
#[library_benchmark]
#[bench::short("0x12.34p+8")]
#[bench::max("0x1.fffffep+127")]
fn icount_bench_hf32(s: &str) -> f32 {
black_box(hf32(s))
}
#[library_benchmark]
#[bench::short("0x12.34p+8")]
#[bench::max("0x1.fffffffffffffp+1023")]
fn icount_bench_hf64(s: &str) -> f64 {
black_box(hf64(s))
}
#[library_benchmark]
#[bench::short("0x12.34p+8")]
#[bench::max("0x1.ffffffffffffffffffffffffffffp+16383")]
fn icount_bench_hf128(s: &str) -> f128 {
black_box(hf128(s))
}
library_benchmark_group!(
name = icount_bench_hf_parse_group;
benchmarks =
icount_bench_hf16,
icount_bench_hf32,
icount_bench_hf64,
icount_bench_hf128
);
#[library_benchmark]
#[bench::short(1.015625)]
#[bench::max(f16::MAX)]
fn icount_bench_print_hf16(x: f16) -> String {
black_box(Hexf(x).to_string())
}
#[library_benchmark]
#[bench::short(1.015625)]
#[bench::max(f32::MAX)]
fn icount_bench_print_hf32(x: f32) -> String {
black_box(Hexf(x).to_string())
}
#[library_benchmark]
#[bench::short(1.015625)]
#[bench::max(f64::MAX)]
fn icount_bench_print_hf64(x: f64) -> String {
black_box(Hexf(x).to_string())
}
#[library_benchmark]
#[bench::short(1.015625)]
#[bench::max(f128::MAX)]
fn icount_bench_print_hf128(x: f128) -> String {
black_box(Hexf(x).to_string())
}
library_benchmark_group!(
name = icount_bench_hf_print_group;
benchmarks =
icount_bench_print_hf16,
icount_bench_print_hf32,
icount_bench_print_hf64,
icount_bench_print_hf128
);
main!(
library_benchmark_groups =
// u256-related benchmarks
icount_bench_u128_widen_mul_group,
icount_bench_u256_add_group,
icount_bench_u256_shr_group,
// Benchmarks not related to public libm math
icount_bench_u128_group,
icount_bench_hf_parse_group,
icount_bench_hf_print_group,
// verify-apilist-start
// verify-sorted-start
icount_bench_acos_group,

View file

@ -55,7 +55,7 @@ where
Op: MathOp<FTy = f32, RustArgs = (f32,)>,
Op::RustArgs: SpacedInput<Op>,
{
let mut ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr, GeneratorKind::QuickSpaced);
let mut ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr, GeneratorKind::Spaced);
plot_one_generator(
out_dir,
&ctx,

View file

@ -51,6 +51,7 @@ where
// Check some special values that aren't included in the above ranges
values.push(Op::FTy::NAN);
values.push(Op::FTy::NEG_NAN);
values.extend(Op::FTy::consts().iter());
// Check around the maximum subnormal value

View file

@ -381,7 +381,7 @@ fn unop_common<F1: Float, F2: Float>(
}
// abs and copysign require signaling NaNs to be propagated, so verify bit equality.
if actual.to_bits() == expected.to_bits() {
if actual.biteq(expected) {
return CheckAction::Custom(Ok(()));
} else {
return CheckAction::Custom(Err(anyhow::anyhow!("NaNs have different bitpatterns")));
@ -444,13 +444,18 @@ fn binop_common<F1: Float, F2: Float>(
expected: F2,
ctx: &CheckCtx,
) -> CheckAction {
// MPFR only has one NaN bitpattern; allow the default `.is_nan()` checks to validate. Skip if
// the first input (magnitude source) is NaN and the output is also a NaN, or if the second
// input (sign source) is NaN.
if ctx.basis == CheckBasis::Mpfr
// MPFR only has one NaN bitpattern; skip tests in cases where the first argument would take
// the sign of a NaN second argument. The default NaN checks cover other cases.
if ctx.base_name == BaseName::Copysign && ctx.basis == CheckBasis::Mpfr && input.1.is_nan() {
return SKIP;
}
// FIXME(#939): this should not be skipped, there is a bug in our implementationi.
if ctx.base_name == BaseName::FmaximumNum
&& ctx.basis == CheckBasis::Mpfr
&& ((input.0.is_nan() && actual.is_nan() && expected.is_nan()) || input.1.is_nan())
{
return SKIP;
return XFAIL_NOCHECK;
}
/* FIXME(#439): our fmin and fmax do not compare signed zeros */

View file

@ -22,13 +22,38 @@ static EXTENSIVE_ITER_OVERRIDE: LazyLock<Option<u64>> = LazyLock::new(|| {
/// Specific tests that need to have a reduced amount of iterations to complete in a reasonable
/// amount of time.
///
/// Contains the itentifier+generator combo to match on, plus the factor to reduce by.
const EXTEMELY_SLOW_TESTS: &[(Identifier, GeneratorKind, u64)] = &[
(Identifier::Fmodf128, GeneratorKind::QuickSpaced, 50),
(Identifier::Fmodf128, GeneratorKind::Extensive, 50),
const EXTREMELY_SLOW_TESTS: &[SlowTest] = &[
SlowTest {
ident: Identifier::Fmodf128,
gen_kind: GeneratorKind::Spaced,
extensive: false,
reduce_factor: 50,
},
SlowTest {
ident: Identifier::Fmodf128,
gen_kind: GeneratorKind::Spaced,
extensive: true,
reduce_factor: 50,
},
];
/// A pattern to match a `CheckCtx`, plus a factor to reduce by.
struct SlowTest {
ident: Identifier,
gen_kind: GeneratorKind,
extensive: bool,
reduce_factor: u64,
}
impl SlowTest {
/// True if the test in `CheckCtx` should be reduced by `reduce_factor`.
fn matches_ctx(&self, ctx: &CheckCtx) -> bool {
self.ident == ctx.fn_ident
&& self.gen_kind == ctx.gen_kind
&& self.extensive == ctx.extensive
}
}
/// Maximum number of iterations to run for a single routine.
///
/// The default value of one greater than `u32::MAX` allows testing single-argument `f32` routines
@ -54,6 +79,7 @@ pub struct CheckCtx {
/// Source of truth for tests.
pub basis: CheckBasis,
pub gen_kind: GeneratorKind,
pub extensive: bool,
/// If specified, this value will override the value returned by [`iteration_count`].
pub override_iterations: Option<u64>,
}
@ -69,12 +95,19 @@ impl CheckCtx {
base_name_str: fn_ident.base_name().as_str(),
basis,
gen_kind,
extensive: false,
override_iterations: None,
};
ret.ulp = crate::default_ulp(&ret);
ret
}
/// Configure that this is an extensive test.
pub fn extensive(mut self, extensive: bool) -> Self {
self.extensive = extensive;
self
}
/// The number of input arguments for this function.
pub fn input_count(&self) -> usize {
self.fn_ident.math_op().rust_sig.args.len()
@ -100,14 +133,17 @@ pub enum CheckBasis {
/// and quantity.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GeneratorKind {
/// Extremes, zeros, nonstandard numbers, etc.
EdgeCases,
Extensive,
QuickSpaced,
/// Spaced by logarithm (floats) or linear (integers).
Spaced,
/// Test inputs from an RNG.
Random,
/// A provided test case list.
List,
}
/// A list of all functions that should get extensive tests.
/// A list of all functions that should get extensive tests, as configured by environment variable.
///
/// This also supports the special test name `all` to run all tests, as well as `all_f16`,
/// `all_f32`, `all_f64`, and `all_f128` to run all tests for a specific float type.
@ -216,17 +252,17 @@ pub fn iteration_count(ctx: &CheckCtx, argnum: usize) -> u64 {
let random_iter_count = domain_iter_count / 100;
let mut total_iterations = match ctx.gen_kind {
GeneratorKind::QuickSpaced => domain_iter_count,
GeneratorKind::Spaced if ctx.extensive => extensive_max_iterations(),
GeneratorKind::Spaced => domain_iter_count,
GeneratorKind::Random => random_iter_count,
GeneratorKind::Extensive => extensive_max_iterations(),
GeneratorKind::EdgeCases | GeneratorKind::List => {
unimplemented!("shoudn't need `iteration_count` for {:?}", ctx.gen_kind)
}
};
// Larger float types get more iterations.
if t_env.large_float_ty && ctx.gen_kind != GeneratorKind::Extensive {
if ctx.gen_kind == GeneratorKind::Extensive {
if t_env.large_float_ty {
if ctx.extensive {
// Extensive already has a pretty high test count.
total_iterations *= 2;
} else {
@ -244,13 +280,13 @@ pub fn iteration_count(ctx: &CheckCtx, argnum: usize) -> u64 {
}
// Some tests are significantly slower than others and need to be further reduced.
if let Some((_id, _gen, scale)) = EXTEMELY_SLOW_TESTS
if let Some(slow) = EXTREMELY_SLOW_TESTS
.iter()
.find(|(id, generator, _scale)| *id == ctx.fn_ident && *generator == ctx.gen_kind)
.find(|slow| slow.matches_ctx(ctx))
{
// However, do not override if the extensive iteration count has been manually set.
if !(ctx.gen_kind == GeneratorKind::Extensive && EXTENSIVE_ITER_OVERRIDE.is_some()) {
total_iterations /= scale;
if !(ctx.extensive && EXTENSIVE_ITER_OVERRIDE.is_some()) {
total_iterations /= slow.reduce_factor;
}
}
@ -279,7 +315,7 @@ pub fn iteration_count(ctx: &CheckCtx, argnum: usize) -> u64 {
let total = ntests.pow(t_env.input_count.try_into().unwrap());
let seed_msg = match ctx.gen_kind {
GeneratorKind::QuickSpaced | GeneratorKind::Extensive => String::new(),
GeneratorKind::Spaced => String::new(),
GeneratorKind::Random => {
format!(
" using `{SEED_ENV}={}`",
@ -327,8 +363,8 @@ pub fn int_range(ctx: &CheckCtx, argnum: usize) -> RangeInclusive<i32> {
let extensive_range = (-0xfff)..=0xfffff;
match ctx.gen_kind {
GeneratorKind::Extensive => extensive_range,
GeneratorKind::QuickSpaced | GeneratorKind::Random => non_extensive_range,
_ if ctx.extensive => extensive_range,
GeneratorKind::Spaced | GeneratorKind::Random => non_extensive_range,
GeneratorKind::EdgeCases => extensive_range,
GeneratorKind::List => unimplemented!("shoudn't need range for {:?}", ctx.gen_kind),
}

View file

@ -312,12 +312,9 @@ where
let mut inner = || -> TestResult {
let mut allowed_ulp = ctx.ulp;
// Forbid overrides if the items came from an explicit list, as long as we are checking
// against either MPFR or the result itself.
let require_biteq = ctx.gen_kind == GeneratorKind::List && ctx.basis != CheckBasis::Musl;
match SpecialCase::check_float(input, actual, expected, ctx) {
_ if require_biteq => (),
// Forbid overrides if the items came from an explicit list
_ if ctx.gen_kind == GeneratorKind::List => (),
CheckAction::AssertSuccess => (),
CheckAction::AssertFailure(msg) => assert_failure_msg = Some(msg),
CheckAction::Custom(res) => return res,
@ -327,12 +324,20 @@ where
// Check when both are NaNs
if actual.is_nan() && expected.is_nan() {
if require_biteq && ctx.basis == CheckBasis::None {
ensure!(
actual.to_bits() == expected.to_bits(),
"mismatched NaN bitpatterns"
);
// Don't assert NaN bitwise equality if:
//
// * Testing against MPFR (there is a single NaN representation)
// * Testing against Musl except for explicit tests (Musl does some NaN quieting)
//
// In these cases, just the check that actual and expected are both NaNs is
// sufficient.
let skip_nan_biteq = ctx.basis == CheckBasis::Mpfr
|| (ctx.basis == CheckBasis::Musl && ctx.gen_kind != GeneratorKind::List);
if !skip_nan_biteq {
ensure!(actual.biteq(expected), "mismatched NaN bitpatterns");
}
// By default, NaNs have nothing special to check.
return Ok(());
} else if actual.is_nan() || expected.is_nan() {

View file

@ -65,7 +65,7 @@ macro_rules! musl_tests {
$(#[$attr])*
fn [< musl_quickspace_ $fn_name >]() {
type Op = libm_test::op::$fn_name::Routine;
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::QuickSpaced);
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::Spaced);
let cases = spaced::get_test_cases::<Op>(&ctx).0;
musl_runner::<Op>(&ctx, cases, musl_math_sys::$fn_name);
}

View file

@ -55,7 +55,7 @@ macro_rules! mp_tests {
$(#[$attr])*
fn [< mp_quickspace_ $fn_name >]() {
type Op = libm_test::op::$fn_name::Routine;
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::QuickSpaced);
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::Spaced);
let cases = spaced::get_test_cases::<Op>(&ctx).0;
mp_runner::<Op>(&ctx, cases);
}

View file

@ -17,7 +17,6 @@ use rayon::prelude::*;
use spaced::SpacedInput;
const BASIS: CheckBasis = CheckBasis::Mpfr;
const GEN_KIND: GeneratorKind = GeneratorKind::Extensive;
/// Run the extensive test suite.
pub fn run() {
@ -77,7 +76,7 @@ where
Op::RustArgs: SpacedInput<Op> + Send,
{
let test_name = format!("mp_extensive_{}", Op::NAME);
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GEN_KIND);
let ctx = CheckCtx::new(Op::IDENTIFIER, BASIS, GeneratorKind::Spaced).extensive(true);
let skip = skip_extensive_test(&ctx);
let runner = move || {

View file

@ -34,7 +34,7 @@ Usage is under the MIT license, available at
### Contribution
Contributions are licensed under both the MIT license and the Apache License,
Version 2.0, available at <htps://www.apache.org/licenses/LICENSE-2.0>. Unless
Version 2.0, available at <https://www.apache.org/licenses/LICENSE-2.0>. Unless
you explicitly state otherwise, any contribution intentionally submitted for
inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as mentioned, without any additional terms or conditions.

View file

@ -30,6 +30,12 @@ pub fn fmaf(mut x: f32, y: f32, z: f32) -> f32 {
x
}
// NB: `frintx` is technically the correct instruction for C's `rint`. However, in Rust (and LLVM
// by default), `rint` is identical to `roundeven` (no fpenv interaction) so we use the
// side-effect-free `frintn`.
//
// In general, C code that calls Rust's libm should assume that fpenv is ignored.
pub fn rint(mut x: f64) -> f64 {
// SAFETY: `frintn` is available with neon and has no side effects.
//

View file

@ -59,9 +59,17 @@ mod tests {
// Not required but we expect it
assert_biteq!(f(F::NAN, F::NAN), F::NAN);
assert_biteq!(f(F::NEG_NAN, F::NAN), F::NAN);
assert_biteq!(f(F::NAN, F::ONE), F::NAN);
assert_biteq!(f(F::NAN, F::NEG_ONE), F::NEG_NAN);
assert_biteq!(f(F::NAN, F::NEG_NAN), F::NEG_NAN);
assert_biteq!(f(F::NEG_NAN, F::NAN), F::NAN);
assert_biteq!(f(F::NEG_NAN, F::ONE), F::NAN);
assert_biteq!(f(F::NEG_NAN, F::NEG_ONE), F::NEG_NAN);
assert_biteq!(f(F::NEG_NAN, F::NEG_NAN), F::NEG_NAN);
assert_biteq!(f(F::ONE, F::NAN), F::ONE);
assert_biteq!(f(F::ONE, F::NEG_NAN), F::NEG_ONE);
assert_biteq!(f(F::NEG_ONE, F::NAN), F::ONE);
assert_biteq!(f(F::NEG_ONE, F::NEG_NAN), F::NEG_ONE);
}
#[test]

View file

@ -1,8 +0,0 @@
/// Sign of Y, magnitude of X (f32)
///
/// Constructs a number with the magnitude (absolute value) of its
/// first argument, `x`, and the sign of its second argument, `y`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn copysignf(x: f32, y: f32) -> f32 {
super::generic::copysign(x, y)
}

View file

@ -1,8 +0,0 @@
/// Sign of Y, magnitude of X (f128)
///
/// Constructs a number with the magnitude (absolute value) of its
/// first argument, `x`, and the sign of its second argument, `y`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn copysignf128(x: f128, y: f128) -> f128 {
super::generic::copysign(x, y)
}

View file

@ -1,8 +0,0 @@
/// Sign of Y, magnitude of X (f16)
///
/// Constructs a number with the magnitude (absolute value) of its
/// first argument, `x`, and the sign of its second argument, `y`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn copysignf16(x: f16, y: f16) -> f16 {
super::generic::copysign(x, y)
}

View file

@ -1,39 +0,0 @@
/// Absolute value (magnitude) (f32)
///
/// Calculates the absolute value (magnitude) of the argument `x`,
/// by direct manipulation of the bit representation of `x`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fabsf(x: f32) -> f32 {
select_implementation! {
name: fabsf,
use_arch: all(target_arch = "wasm32", intrinsics_enabled),
args: x,
}
super::generic::fabs(x)
}
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
#[cfg(not(target_arch = "powerpc64"))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_check() {
assert_eq!(fabsf(-1.0), 1.0);
assert_eq!(fabsf(2.8), 2.8);
}
/// The spec: https://en.cppreference.com/w/cpp/numeric/math/fabs
#[test]
fn spec_tests() {
assert!(fabsf(f32::NAN).is_nan());
for f in [0.0, -0.0].iter().copied() {
assert_eq!(fabsf(f), 0.0);
}
for f in [f32::INFINITY, f32::NEG_INFINITY].iter().copied() {
assert_eq!(fabsf(f), f32::INFINITY);
}
}
}

View file

@ -1,31 +0,0 @@
/// Absolute value (magnitude) (f128)
///
/// Calculates the absolute value (magnitude) of the argument `x`,
/// by direct manipulation of the bit representation of `x`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fabsf128(x: f128) -> f128 {
super::generic::fabs(x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_check() {
assert_eq!(fabsf128(-1.0), 1.0);
assert_eq!(fabsf128(2.8), 2.8);
}
/// The spec: https://en.cppreference.com/w/cpp/numeric/math/fabs
#[test]
fn spec_tests() {
assert!(fabsf128(f128::NAN).is_nan());
for f in [0.0, -0.0].iter().copied() {
assert_eq!(fabsf128(f), 0.0);
}
for f in [f128::INFINITY, f128::NEG_INFINITY].iter().copied() {
assert_eq!(fabsf128(f), f128::INFINITY);
}
}
}

View file

@ -1,31 +0,0 @@
/// Absolute value (magnitude) (f16)
///
/// Calculates the absolute value (magnitude) of the argument `x`,
/// by direct manipulation of the bit representation of `x`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fabsf16(x: f16) -> f16 {
super::generic::fabs(x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_check() {
assert_eq!(fabsf16(-1.0), 1.0);
assert_eq!(fabsf16(2.8), 2.8);
}
/// The spec: https://en.cppreference.com/w/cpp/numeric/math/fabs
#[test]
fn spec_tests() {
assert!(fabsf16(f16::NAN).is_nan());
for f in [0.0, -0.0].iter().copied() {
assert_eq!(fabsf16(f), 0.0);
}
for f in [f16::INFINITY, f16::NEG_INFINITY].iter().copied() {
assert_eq!(fabsf16(f), f16::INFINITY);
}
}
}

View file

@ -1,12 +0,0 @@
/// Positive difference (f32)
///
/// Determines the positive difference between arguments, returning:
/// * x - y if x > y, or
/// * +0 if x <= y, or
/// * NAN if either argument is NAN.
///
/// A range error may occur.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fdimf(x: f32, y: f32) -> f32 {
super::generic::fdim(x, y)
}

View file

@ -1,12 +0,0 @@
/// Positive difference (f128)
///
/// Determines the positive difference between arguments, returning:
/// * x - y if x > y, or
/// * +0 if x <= y, or
/// * NAN if either argument is NAN.
///
/// A range error may occur.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fdimf128(x: f128, y: f128) -> f128 {
super::generic::fdim(x, y)
}

View file

@ -1,12 +0,0 @@
/// Positive difference (f16)
///
/// Determines the positive difference between arguments, returning:
/// * x - y if x > y, or
/// * +0 if x <= y, or
/// * NAN if either argument is NAN.
///
/// A range error may occur.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fdimf16(x: f16, y: f16) -> f16 {
super::generic::fdim(x, y)
}

View file

@ -1,13 +0,0 @@
/// Floor (f32)
///
/// Finds the nearest integer less than or equal to `x`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn floorf(x: f32) -> f32 {
select_implementation! {
name: floorf,
use_arch: all(target_arch = "wasm32", intrinsics_enabled),
args: x,
}
return super::generic::floor(x);
}

View file

@ -1,7 +0,0 @@
/// Floor (f128)
///
/// Finds the nearest integer less than or equal to `x`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn floorf128(x: f128) -> f128 {
return super::generic::floor(x);
}

View file

@ -1,7 +0,0 @@
/// Floor (f16)
///
/// Finds the nearest integer less than or equal to `x`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn floorf16(x: f16) -> f16 {
return super::generic::floor(x);
}

View file

@ -1,5 +0,0 @@
/// Calculate the remainder of `x / y`, the precise result of `x - trunc(x / y) * y`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fmodf(x: f32, y: f32) -> f32 {
super::generic::fmod(x, y)
}

View file

@ -1,5 +0,0 @@
/// Calculate the remainder of `x / y`, the precise result of `x - trunc(x / y) * y`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fmodf128(x: f128, y: f128) -> f128 {
super::generic::fmod(x, y)
}

View file

@ -1,5 +0,0 @@
/// Calculate the remainder of `x / y`, the precise result of `x - trunc(x / y) * y`.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fmodf16(x: f16, y: f16) -> f16 {
super::generic::fmod(x, y)
}

View file

@ -17,7 +17,7 @@ pub fn fmaximum<F: Float>(x: F, y: F) -> F {
x
} else if y.is_nan() {
y
} else if x > y || (y.to_bits() == F::NEG_ZERO.to_bits() && x.is_sign_positive()) {
} else if x > y || (y.biteq(F::NEG_ZERO) && x.is_sign_positive()) {
x
} else {
y

View file

@ -15,12 +15,11 @@ use crate::support::Float;
#[inline]
pub fn fmaximum_num<F: Float>(x: F, y: F) -> F {
let res =
if x.is_nan() || x < y || (x.to_bits() == F::NEG_ZERO.to_bits() && y.is_sign_positive()) {
y
} else {
x
};
let res = if x.is_nan() || x < y || (x.biteq(F::NEG_ZERO) && y.is_sign_positive()) {
y
} else {
x
};
// Canonicalize
res * F::ONE

View file

@ -17,7 +17,7 @@ pub fn fminimum<F: Float>(x: F, y: F) -> F {
x
} else if y.is_nan() {
y
} else if x < y || (x.to_bits() == F::NEG_ZERO.to_bits() && y.is_sign_positive()) {
} else if x < y || (x.biteq(F::NEG_ZERO) && y.is_sign_positive()) {
x
} else {
y

View file

@ -15,12 +15,11 @@ use crate::support::Float;
#[inline]
pub fn fminimum_num<F: Float>(x: F, y: F) -> F {
let res =
if y.is_nan() || x < y || (x.to_bits() == F::NEG_ZERO.to_bits() && y.is_sign_positive()) {
x
} else {
y
};
let res = if y.is_nan() || x < y || (x.biteq(F::NEG_ZERO) && y.is_sign_positive()) {
x
} else {
y
};
// Canonicalize
res * F::ONE

View file

@ -1,4 +0,0 @@
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn ldexpf(x: f32, n: i32) -> f32 {
super::scalbnf(x, n)
}

View file

@ -1,4 +0,0 @@
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn ldexpf128(x: f128, n: i32) -> f128 {
super::scalbnf128(x, n)
}

View file

@ -1,4 +0,0 @@
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn ldexpf16(x: f16, n: i32) -> f16 {
super::scalbnf16(x, n)
}

View file

@ -1,5 +0,0 @@
/// Round `x` to the nearest integer, breaking ties away from zero.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn roundf(x: f32) -> f32 {
super::generic::round(x)
}

View file

@ -1,5 +0,0 @@
/// Round `x` to the nearest integer, breaking ties away from zero.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn roundf128(x: f128) -> f128 {
super::generic::round(x)
}

View file

@ -1,5 +0,0 @@
/// Round `x` to the nearest integer, breaking ties away from zero.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn roundf16(x: f16) -> f16 {
super::generic::round(x)
}

View file

@ -1,4 +0,0 @@
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn scalbnf(x: f32, n: i32) -> f32 {
super::generic::scalbn(x, n)
}

View file

@ -1,4 +0,0 @@
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn scalbnf128(x: f128, n: i32) -> f128 {
super::generic::scalbn(x, n)
}

View file

@ -1,4 +0,0 @@
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn scalbnf16(x: f16, n: i32) -> f16 {
super::generic::scalbn(x, n)
}

View file

@ -1,15 +0,0 @@
/// The square root of `x` (f32).
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn sqrtf(x: f32) -> f32 {
select_implementation! {
name: sqrtf,
use_arch: any(
all(target_arch = "aarch64", target_feature = "neon"),
all(target_arch = "wasm32", intrinsics_enabled),
target_feature = "sse2"
),
args: x,
}
super::generic::sqrt(x)
}

View file

@ -1,5 +0,0 @@
/// The square root of `x` (f128).
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn sqrtf128(x: f128) -> f128 {
return super::generic::sqrt(x);
}

View file

@ -1,11 +0,0 @@
/// The square root of `x` (f16).
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn sqrtf16(x: f16) -> f16 {
select_implementation! {
name: sqrtf16,
use_arch: all(target_arch = "aarch64", target_feature = "fp16"),
args: x,
}
return super::generic::sqrt(x);
}

View file

@ -6,6 +6,7 @@ use super::int_traits::{CastFrom, Int, MinInt};
/// Trait for some basic operations on floats
// #[allow(dead_code)]
#[allow(dead_code)] // Some constants are only used with tests
pub trait Float:
Copy
+ fmt::Debug

View file

@ -1,8 +1,6 @@
//! Utilities for working with hex float formats.
use core::fmt;
use super::{Float, Round, Status, f32_from_bits, f64_from_bits};
use super::{Round, Status, f32_from_bits, f64_from_bits};
/// Construct a 16-bit float from hex float representation (C-style)
#[cfg(f16_enabled)]
@ -352,132 +350,142 @@ const fn u128_ilog2(v: u128) -> u32 {
u128::BITS - 1 - v.leading_zeros()
}
/// Format a floating point number as its IEEE hex (`%a`) representation.
pub struct Hexf<F>(pub F);
#[cfg(any(test, feature = "unstable-public-internals"))]
mod hex_fmt {
use core::fmt;
// Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs
#[cfg(not(feature = "compiler-builtins"))]
fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if x.is_sign_negative() {
write!(f, "-")?;
use crate::support::Float;
/// Format a floating point number as its IEEE hex (`%a`) representation.
pub struct Hexf<F>(pub F);
// Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs
#[cfg(not(feature = "compiler-builtins"))]
pub(super) fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if x.is_sign_negative() {
write!(f, "-")?;
}
if x.is_nan() {
return write!(f, "NaN");
} else if x.is_infinite() {
return write!(f, "inf");
} else if *x == F::ZERO {
return write!(f, "0x0p+0");
}
let mut exponent = x.exp_unbiased();
let sig = x.to_bits() & F::SIG_MASK;
let bias = F::EXP_BIAS as i32;
// The mantissa MSB needs to be shifted up to the nearest nibble.
let mshift = (4 - (F::SIG_BITS % 4)) % 4;
let sig = sig << mshift;
// The width is rounded up to the nearest char (4 bits)
let mwidth = (F::SIG_BITS as usize + 3) / 4;
let leading = if exponent == -bias {
// subnormal number means we shift our output by 1 bit.
exponent += 1;
"0."
} else {
"1."
};
write!(f, "0x{leading}{sig:0mwidth$x}p{exponent:+}")
}
if x.is_nan() {
return write!(f, "NaN");
} else if x.is_infinite() {
return write!(f, "inf");
} else if *x == F::ZERO {
return write!(f, "0x0p+0");
#[cfg(feature = "compiler-builtins")]
pub(super) fn fmt_any_hex<F: Float>(_x: &F, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
unimplemented!()
}
let mut exponent = x.exp_unbiased();
let sig = x.to_bits() & F::SIG_MASK;
impl<F: Float> fmt::LowerHex for Hexf<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt_any_hex(&self.0, f)
}
}
}
}
let bias = F::EXP_BIAS as i32;
// The mantissa MSB needs to be shifted up to the nearest nibble.
let mshift = (4 - (F::SIG_BITS % 4)) % 4;
let sig = sig << mshift;
// The width is rounded up to the nearest char (4 bits)
let mwidth = (F::SIG_BITS as usize + 3) / 4;
let leading = if exponent == -bias {
// subnormal number means we shift our output by 1 bit.
exponent += 1;
"0."
} else {
"1."
};
impl<F: Float> fmt::LowerHex for Hexf<(F, F)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1))
}
}
}
}
write!(f, "0x{leading}{sig:0mwidth$x}p{exponent:+}")
}
impl<F: Float> fmt::LowerHex for Hexf<(F, i32)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1))
}
}
}
}
#[cfg(feature = "compiler-builtins")]
fn fmt_any_hex<F: Float>(_x: &F, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
unimplemented!()
}
impl fmt::LowerHex for Hexf<i32> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt::LowerHex::fmt(&self.0, f)
}
}
}
}
impl<F: Float> fmt::LowerHex for Hexf<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt_any_hex(&self.0, f)
impl<T> fmt::Debug for Hexf<T>
where
Hexf<T>: fmt::LowerHex,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt::LowerHex::fmt(self, f)
}
}
}
}
impl<T> fmt::Display for Hexf<T>
where
Hexf<T>: fmt::LowerHex,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt::LowerHex::fmt(self, f)
}
}
}
}
}
impl<F: Float> fmt::LowerHex for Hexf<(F, F)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1))
}
}
}
}
impl<F: Float> fmt::LowerHex for Hexf<(F, i32)> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1))
}
}
}
}
impl fmt::LowerHex for Hexf<i32> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt::LowerHex::fmt(&self.0, f)
}
}
}
}
impl<T> fmt::Debug for Hexf<T>
where
Hexf<T>: fmt::LowerHex,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt::LowerHex::fmt(self, f)
}
}
}
}
impl<T> fmt::Display for Hexf<T>
where
Hexf<T>: fmt::LowerHex,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
fmt::LowerHex::fmt(self, f)
}
}
}
}
#[cfg(any(test, feature = "unstable-public-internals"))]
pub use hex_fmt::*;
#[cfg(test)]
mod parse_tests {
@ -1064,6 +1072,7 @@ mod print_tests {
use std::string::ToString;
use super::*;
use crate::support::Float;
#[test]
#[cfg(f16_enabled)]

View file

@ -1,6 +1,7 @@
use core::{cmp, fmt, ops};
/// Minimal integer implementations needed on all integer types, including wide integers.
#[allow(dead_code)] // Some constants are only used with tests
pub trait MinInt:
Copy
+ fmt::Debug
@ -78,6 +79,7 @@ pub trait Int:
fn unsigned(self) -> Self::Unsigned;
fn from_unsigned(unsigned: Self::Unsigned) -> Self;
fn abs(self) -> Self;
fn unsigned_abs(self) -> Self::Unsigned;
fn from_bool(b: bool) -> Self;
@ -203,6 +205,10 @@ macro_rules! int_impl {
unimplemented!()
}
fn unsigned_abs(self) -> Self {
unimplemented!()
}
// It makes writing macros easier if this is implemented for both signed and unsigned
#[allow(clippy::wrong_self_convention)]
fn from_unsigned(me: $uty) -> Self {
@ -242,6 +248,10 @@ macro_rules! int_impl {
self.abs()
}
fn unsigned_abs(self) -> Self::Unsigned {
self.unsigned_abs()
}
fn from_unsigned(me: $uty) -> Self {
me as $ity
}
@ -365,14 +375,19 @@ impl_h_int!(
/// Trait to express (possibly lossy) casting of integers
pub trait CastInto<T: Copy>: Copy {
/// By default, casts should be exact.
#[track_caller]
fn cast(self) -> T;
/// Call for casts that are expected to truncate.
///
/// In practice, this is exactly the same as `cast`; the main difference is to document intent
/// in code. `cast` may panic in debug mode.
fn cast_lossy(self) -> T;
}
pub trait CastFrom<T: Copy>: Copy {
/// By default, casts should be exact.
#[track_caller]
fn cast_from(value: T) -> Self;
/// Call for casts that are expected to truncate.

View file

@ -137,12 +137,12 @@ macro_rules! hf128 {
#[cfg(test)]
macro_rules! assert_biteq {
($left:expr, $right:expr, $($tt:tt)*) => {{
use $crate::support::Int;
let l = $left;
let r = $right;
let bits = Int::leading_zeros(l.to_bits() - l.to_bits()); // hack to get the width from the value
// hack to get width from a value
let bits = $crate::support::Int::leading_zeros(l.to_bits() - l.to_bits());
assert!(
l.biteq(r),
$crate::support::Float::biteq(l, r),
"{}\nl: {l:?} ({lb:#0width$x})\nr: {r:?} ({rb:#0width$x})",
format_args!($($tt)*),
lb = l.to_bits(),

View file

@ -11,10 +11,14 @@ mod int_traits;
#[allow(unused_imports)]
pub use big::{i256, u256};
#[allow(unused_imports)]
pub(crate) use cfg_if;
pub use env::{FpResult, Round, Status};
#[allow(unused_imports)]
pub use float_traits::{DFloat, Float, HFloat, IntTy};
pub(crate) use float_traits::{f32_from_bits, f64_from_bits};
#[cfg(any(test, feature = "unstable-public-internals"))]
pub use hex_float::Hexf;
#[cfg(f16_enabled)]
#[allow(unused_imports)]
pub use hex_float::hf16;
@ -22,7 +26,7 @@ pub use hex_float::hf16;
#[allow(unused_imports)]
pub use hex_float::hf128;
#[allow(unused_imports)]
pub use hex_float::{Hexf, hf32, hf64};
pub use hex_float::{hf32, hf64};
pub use int_traits::{CastFrom, CastInto, DInt, HInt, Int, MinInt};
/// Hint to the compiler that the current path is cold.

View file

@ -1,23 +0,0 @@
/// Rounds the number toward 0 to the closest integral value (f32).
///
/// This effectively removes the decimal part of the number, leaving the integral part.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn truncf(x: f32) -> f32 {
select_implementation! {
name: truncf,
use_arch: all(target_arch = "wasm32", intrinsics_enabled),
args: x,
}
super::generic::trunc(x)
}
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
#[cfg(not(target_arch = "powerpc64"))]
#[cfg(test)]
mod tests {
#[test]
fn sanity_check() {
assert_eq!(super::truncf(1.1), 1.0);
}
}

View file

@ -1,7 +0,0 @@
/// Rounds the number toward 0 to the closest integral value (f128).
///
/// This effectively removes the decimal part of the number, leaving the integral part.
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn truncf128(x: f128) -> f128 {
super::generic::trunc(x)
}

Some files were not shown because too many files have changed in this diff Show more