Merge from rustc
This commit is contained in:
commit
c44bc10b67
291 changed files with 4610 additions and 2821 deletions
|
|
@ -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
|
||||
|
|
|
|||
3
library/compiler-builtins/.gitignore
vendored
3
library/compiler-builtins/.gitignore
vendored
|
|
@ -14,3 +14,6 @@ iai-home
|
|||
*.bk
|
||||
*.rs.bk
|
||||
.#*
|
||||
|
||||
# Manually managed
|
||||
crates/musl-math-sys/musl
|
||||
|
|
|
|||
4
library/compiler-builtins/.gitmodules
vendored
4
library/compiler-builtins/.gitmodules
vendored
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
15
library/compiler-builtins/ci/update-musl.sh
Executable file
15
library/compiler-builtins/ci/update-musl.sh
Executable 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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)*
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
7
library/compiler-builtins/crates/josh-sync/Cargo.toml
Normal file
7
library/compiler-builtins/crates/josh-sync/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "josh-sync"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
directories = "6.0.0"
|
||||
45
library/compiler-builtins/crates/josh-sync/src/main.rs
Normal file
45
library/compiler-builtins/crates/josh-sync/src/main.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
371
library/compiler-builtins/crates/josh-sync/src/sync.rs
Normal file
371
library/compiler-builtins/crates/josh-sync/src/sync.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ license = "MIT OR Apache-2.0"
|
|||
libm = { path = "../../libm" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.2.16"
|
||||
cc = "1.2.25"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
|||
13
library/compiler-builtins/crates/symbol-check/Cargo.toml
Normal file
13
library/compiler-builtins/crates/symbol-check/Cargo.toml
Normal 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"]
|
||||
232
library/compiler-builtins/crates/symbol-check/src/main.rs
Normal file
232
library/compiler-builtins/crates/symbol-check/src/main.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 || {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue