Auto merge of #153866 - tgross35:update-builtins, r=tgross35

compiler-builtins subtree update

Subtree update of `compiler-builtins` to https://github.com/rust-lang/compiler-builtins/commit/8173070e590c04a0d36ee9f3e0397c6006596976.

Created using https://github.com/rust-lang/josh-sync.

r? @ghost
This commit is contained in:
bors
2026-03-14 16:56:25 +00:00
80 changed files with 3810 additions and 745 deletions
+9 -9
View File
@@ -13,7 +13,7 @@ env:
RUSTDOCFLAGS: -Dwarnings
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: full
BENCHMARK_RUSTC: nightly-2025-12-01 # Pin the toolchain for reproducable results
BENCHMARK_RUSTC: nightly-2026-02-10 # Pin the toolchain for reproducable results
jobs:
# Determine which tests should be run based on changed files.
@@ -70,14 +70,12 @@ jobs:
os: ubuntu-24.04
- target: powerpc64le-unknown-linux-gnu
os: ubuntu-24.04
# - target: powerpc64le-unknown-linux-gnu
# os: ubuntu-24.04-ppc64le
# # FIXME(rust#151807): remove once PPC builds work again.
# channel: nightly-2026-01-23
- target: powerpc64le-unknown-linux-gnu
os: ubuntu-24.04-ppc64le
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-24.04
# - target: s390x-unknown-linux-gnu
# os: ubuntu-24.04-s390x
- target: s390x-unknown-linux-gnu
os: ubuntu-24.04-s390x
- target: thumbv6m-none-eabi
os: ubuntu-24.04
- target: thumbv7em-none-eabi
@@ -289,7 +287,7 @@ jobs:
path: ${{ env.BASELINE_NAME }}.tar.xz
- name: Run wall time benchmarks
run: ./ci/bench-runtime.sh
run: ./ci/bench-walltime.sh
- name: Print test logs if available
if: always()
@@ -303,7 +301,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Rust (rustup)
run: rustup update nightly --no-self-update && rustup default nightly
# FIXME(ci): not working in the 2026-02-11 nightly
# https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/build-script-build.20contains.20outdated.20or.20invalid.20JSON/with/573426109
run: rustup update nightly-2026-02-10 --no-self-update && rustup default nightly-2026-02-10
shell: bash
- run: rustup component add miri
- run: cargo miri setup
+5 -1
View File
@@ -1,7 +1,11 @@
# Rust files
Cargo.lock
target
# We have a couple of potential Cargo.lock files, but we only the root
# workspace should have dependencies other than (optional) `cc`.
Cargo.lock
!/Cargo.lock
# Sources for external files
compiler-rt
*.tar.gz
+1655
View File
@@ -0,0 +1,1655 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "alloca"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
dependencies = [
"cc",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "anyhow"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "assert_cmd"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
dependencies = [
"anstyle",
"bstr",
"libc",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "az"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "bstr"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "builtins-test"
version = "0.1.0"
dependencies = [
"compiler_builtins",
"criterion",
"gungraun",
"paste",
"rand_xoshiro",
"rustc_apfloat",
"test",
"utest-cortex-m-qemu",
"utest-macros",
]
[[package]]
name = "bumpalo"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core",
]
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compiler_builtins"
version = "0.1.160"
dependencies = [
"cc",
]
[[package]]
name = "console"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"windows-sys 0.61.2",
]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
dependencies = [
"alloca",
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"itertools",
"num-traits",
"oorandom",
"page_size",
"plotters",
"regex",
"serde",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "escape8259"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6"
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getopts"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"rand_core",
"wasip2",
"wasip3",
"wasm-bindgen",
]
[[package]]
name = "gmp-mpfr-sys"
version = "1.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]]
name = "gungraun"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c1bbe46f51c63bc08a1fac0ee0c530a77c961613a86ecf828ab1b0ffc6687a"
dependencies = [
"bincode",
"derive_more",
"gungraun-macros",
"gungraun-runner",
]
[[package]]
name = "gungraun-macros"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdccd089c36fb2ee66ef0eb7b1baa3ce7e7878a8eae682d9c8c368869ff6eca1"
dependencies = [
"derive_more",
"proc-macro-error2",
"proc-macro2",
"quote",
"rustc_version",
"serde",
"serde_json",
"syn",
]
[[package]]
name = "gungraun-runner"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6da6487203fa53ae6b1c8fead642fe79a3199464b0dd1337635594d675a9ac05"
dependencies = [
"serde",
]
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "indicatif"
version = "0.18.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb"
dependencies = [
"console",
"portable-atomic",
"unit-prefix",
"web-time",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "js-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libm"
version = "0.2.16"
dependencies = [
"no-panic",
]
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libm-macros"
version = "0.1.0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "libm-test"
version = "0.1.0"
dependencies = [
"anyhow",
"criterion",
"getrandom",
"gmp-mpfr-sys",
"gungraun",
"indicatif",
"libm 0.2.16",
"libm-macros",
"libtest-mimic",
"musl-math-sys",
"paste",
"rand",
"rand_chacha",
"rayon",
"rug",
]
[[package]]
name = "libtest-mimic"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33"
dependencies = [
"anstream",
"anstyle",
"clap",
"escape8259",
]
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "musl-math-sys"
version = "0.1.0"
dependencies = [
"cc",
"libm 0.2.16",
]
[[package]]
name = "no-panic"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f967505aabc8af5752d098c34146544a43684817cdba8f9725b292530cabbf53"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.38.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc"
dependencies = [
"flate2",
"memchr",
"ruzstd",
"wasmparser 0.243.0",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "oorandom"
version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "panic-handler"
version = "0.1.0"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "plotters"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "predicates"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
dependencies = [
"anstyle",
"difflib",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
[[package]]
name = "predicates-tree"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
dependencies = [
"predicates-core",
"termtree",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"chacha20",
"getrandom",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "rand_xoshiro"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f0b2cc7bfeef8f0320ca45f88b00157a03c67137022d59393614352d6bf4312"
dependencies = [
"rand_core",
]
[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]]
name = "rug"
version = "1.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de190ec858987c79cad4da30e19e546139b3339331282832af004d0ea7829639"
dependencies = [
"az",
"gmp-mpfr-sys",
"libc",
"libm 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc_apfloat"
version = "0.2.3+llvm-462a31f5a5ab"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486c2179b4796f65bfe2ee33679acf0927ac83ecf583ad6c91c3b4570911b9ad"
dependencies = [
"bitflags",
"smallvec",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ruzstd"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01"
dependencies = [
"twox-hash",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "sc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b"
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "symbol-check"
version = "0.1.0"
dependencies = [
"assert_cmd",
"cc",
"getopts",
"object",
"regex",
"serde_json",
"tempfile",
]
[[package]]
name = "syn"
version = "2.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "termtree"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "test"
version = "0.1.0"
source = "git+https://github.com/japaric/utest#e32073e2b078e3bee46001c13ae4c1acf368d762"
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "twox-hash"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unit-prefix"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
[[package]]
name = "utest-cortex-m-qemu"
version = "0.1.0"
source = "git+https://github.com/japaric/utest#e32073e2b078e3bee46001c13ae4c1acf368d762"
dependencies = [
"sc",
]
[[package]]
name = "utest-macros"
version = "0.1.0"
source = "git+https://github.com/japaric/utest#e32073e2b078e3bee46001c13ae4c1acf368d762"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "util"
version = "0.1.0"
dependencies = [
"cfg-if",
"libm 0.2.16",
"libm-macros",
"libm-test",
"musl-math-sys",
"rug",
]
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser 0.244.0",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser 0.244.0",
]
[[package]]
name = "wasmparser"
version = "0.243.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d"
dependencies = [
"bitflags",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "web-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser 0.244.0",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser 0.244.0",
]
[[package]]
name = "zerocopy"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+13 -11
View File
@@ -34,12 +34,14 @@ exclude = [
[workspace.dependencies]
anyhow = "1.0.101"
assert_cmd = "2.1.2"
cc = "1.2.55"
cc = "1.2.56"
cfg-if = "1.0.4"
compiler_builtins = { path = "builtins-shim", default-features = false }
criterion = { version = "0.6.0", default-features = false, features = ["cargo_bench_support"] }
getrandom = "0.3.4"
criterion = { version = "0.8.2", default-features = false, features = ["cargo_bench_support"] }
getopts = "0.2.24"
getrandom = "0.4.1"
gmp-mpfr-sys = { version = "1.6.8", default-features = false }
gungraun = "0.17.0"
gungraun = "0.17.2"
heck = "0.5.0"
indicatif = { version = "0.18.3", default-features = false }
libm = { path = "libm", default-features = false }
@@ -47,22 +49,22 @@ libm-macros = { path = "crates/libm-macros" }
libm-test = { path = "libm-test", default-features = false }
libtest-mimic = "0.8.1"
musl-math-sys = { path = "crates/musl-math-sys" }
no-panic = "0.1.35"
object = { version = "0.37.3", features = ["wasm"] }
no-panic = "0.1.36"
object = { version = "0.38.1", features = ["wasm"] }
panic-handler = { path = "crates/panic-handler" }
paste = "1.0.15"
proc-macro2 = "1.0.106"
quote = "1.0.44"
rand = "0.9.2"
rand_chacha = "0.9.0"
rand_xoshiro = "0.7"
rand = "0.10.0"
rand_chacha = "0.10.0"
rand_xoshiro = "0.8"
rayon = "1.11.0"
regex = "1.12.3"
rug = { version = "1.28.1", default-features = false, features = ["float", "integer", "std"] }
rustc_apfloat = "0.2.3"
serde_json = "1.0.149"
syn = "2.0.114"
tempfile = "3.24.0"
syn = "2.0.115"
tempfile = "3.25.0"
[profile.release]
panic = "abort"
@@ -3,10 +3,8 @@
// compiling a C implementation and forget to implement that intrinsic in Rust, this file will fail
// to link due to the missing intrinsic (symbol).
#![allow(unused_features)]
#![allow(internal_features)]
#![allow(internal_features, unused_features)]
#![deny(dead_code)]
#![feature(allocator_api)]
#![feature(f128)]
#![feature(f16)]
#![feature(lang_items)]
@@ -16,11 +16,11 @@ rand_xoshiro.workspace = true
# To compare float builtins against
rustc_apfloat.workspace = true
# Really a dev dependency, but dev dependencies can't be optional
# Really dev dependencies, but those can't be optional
criterion = { workspace = true, optional = true }
gungraun = { workspace = true, optional = true }
[dev-dependencies]
criterion.workspace = true
paste.workspace = true
[target.'cfg(all(target_arch = "arm", not(any(target_env = "gnu", target_env = "musl")), target_os = "linux"))'.dev-dependencies]
@@ -46,8 +46,10 @@ no-sys-f16 = ["no-sys-f16-f64-convert"]
# Enable icount benchmarks (requires gungraun-runner and valgrind locally)
icount = ["dep:gungraun"]
# Enable report generation without bringing in more dependencies by default
benchmarking-reports = ["criterion/plotters", "criterion/html_reports"]
# Config for wall time benchmarks. Plotters and html_reports bring in extra
# deps so are off by default for CI.
benchmarking-reports = ["walltime", "criterion/plotters", "criterion/html_reports"]
walltime = ["dep:criterion"]
# NOTE: benchmarks must be run with `--no-default-features` or with
# `-p builtins-test`, otherwise the default `compiler-builtins` feature
@@ -57,38 +59,47 @@ benchmarking-reports = ["criterion/plotters", "criterion/html_reports"]
[[bench]]
name = "float_add"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_sub"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_mul"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_div"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_cmp"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_conv"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_extend"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_trunc"
harness = false
required-features = ["walltime"]
[[bench]]
name = "float_pow"
harness = false
required-features = ["walltime"]
[[bench]]
name = "mem_icount"
@@ -22,7 +22,7 @@
use compiler_builtins::float::Float;
use compiler_builtins::int::{Int, MinInt};
use rand_xoshiro::Xoshiro128StarStar;
use rand_xoshiro::rand_core::{RngCore, SeedableRng};
use rand_xoshiro::rand_core::{Rng, SeedableRng};
/// Sets the number of fuzz iterations run for most tests. In practice, the vast majority of bugs
/// are caught by the edge case testers. Most of the remaining bugs triggered by more complex
@@ -1,4 +1,4 @@
#![allow(unused_macros)]
#![allow(unused_macros, unused_features)]
#![cfg_attr(f16_enabled, feature(f16))]
#![cfg_attr(f128_enabled, feature(f128))]
@@ -1,8 +1,9 @@
#![cfg_attr(f128_enabled, feature(f128))]
#![cfg_attr(f16_enabled, feature(f16))]
// makes configuration easier
#![allow(unused_macros)]
#![allow(unused_features)]
#![allow(unused_imports)]
#![allow(unused_macros)]
use builtins_test::*;
use compiler_builtins::float::Float;
@@ -1,5 +1,5 @@
#![feature(f128)]
#![allow(unused_macros)]
#![allow(unused_macros, unused_features)]
use builtins_test::*;
use compiler_builtins::int::sdiv::{__divmoddi4, __divmodsi4, __divmodti4};
@@ -1,4 +1,4 @@
#![allow(unused_macros)]
#![allow(unused_macros, unused_features)]
#![cfg_attr(f128_enabled, feature(f128))]
#[cfg_attr(x86_no_sse, allow(unused))]
@@ -1,30 +1,71 @@
#![allow(unused_features)]
#![feature(decl_macro)] // so we can use pub(super)
#![feature(macro_metavar_expr_concat)]
#![cfg(all(target_arch = "aarch64", target_os = "linux"))]
#![cfg(all(target_arch = "aarch64", feature = "mangled-names"))]
use std::sync::Mutex;
use compiler_builtins::aarch64_outline_atomics::{get_have_lse_atomics, set_have_lse_atomics};
use compiler_builtins::int::{Int, MinInt};
use compiler_builtins::{foreach_bytes, foreach_ordering};
#[track_caller]
fn with_maybe_lse_atomics(use_lse: bool, f: impl FnOnce()) {
// Ensure tests run in parallel don't interleave global settings
static LOCK: Mutex<()> = Mutex::new(());
let _g = LOCK.lock().unwrap();
let old = get_have_lse_atomics();
// safety: as the caller of the unsafe fn `set_have_lse_atomics`, we
// have to ensure the CPU supports LSE. This is why we make this assertion.
if use_lse || old {
assert!(std::arch::is_aarch64_feature_detected!("lse"));
}
unsafe { set_have_lse_atomics(use_lse) };
f();
unsafe { set_have_lse_atomics(old) };
}
pub fn run_fuzz_tests_with_lse_variants<I: Int, F: Fn(I, I) + Copy>(n: u32, f: F)
where
<I as MinInt>::Unsigned: Int,
{
// We use `fuzz_2` because our subject function `f` requires two inputs
let test_fn = || {
builtins_test::fuzz_2(n, f);
};
// Always run without LSE
with_maybe_lse_atomics(false, test_fn);
// Conditionally run with LSE
if std::arch::is_aarch64_feature_detected!("lse") {
with_maybe_lse_atomics(true, test_fn);
}
}
/// Translate a byte size to a Rust type.
macro int_ty {
(1) => { i8 },
(2) => { i16 },
(4) => { i32 },
(8) => { i64 },
(16) => { i128 }
(1) => { u8 },
(2) => { u16 },
(4) => { u32 },
(8) => { u64 },
(16) => { u128 }
}
mod cas {
pub(super) macro test($_ordering:ident, $bytes:tt, $name:ident) {
#[test]
fn $name() {
builtins_test::fuzz_2(10000, |expected: super::int_ty!($bytes), new| {
crate::run_fuzz_tests_with_lse_variants(10000, |expected: super::int_ty!($bytes), new| {
let mut target = expected.wrapping_add(10);
let ret: super::int_ty!($bytes) = unsafe {
compiler_builtins::aarch64_outline_atomics::$name::$name(
expected,
new,
&mut target,
)
};
assert_eq!(
unsafe {
compiler_builtins::aarch64_outline_atomics::$name::$name(
expected,
new,
&mut target,
)
},
ret,
expected.wrapping_add(10),
"return value should always be the previous value",
);
@@ -35,15 +76,17 @@ fn $name() {
);
target = expected;
let ret: super::int_ty!($bytes) = unsafe {
compiler_builtins::aarch64_outline_atomics::$name::$name(
expected,
new,
&mut target,
)
};
assert_eq!(
unsafe {
compiler_builtins::aarch64_outline_atomics::$name::$name(
expected,
new,
&mut target,
)
},
expected
ret,
expected,
"the new return value should always be the previous value (i.e. the first parameter passed to the function)",
);
assert_eq!(target, new, "should have updated target");
});
@@ -59,16 +102,21 @@ mod swap {
pub(super) macro test($_ordering:ident, $bytes:tt, $name:ident) {
#[test]
fn $name() {
builtins_test::fuzz_2(10000, |left: super::int_ty!($bytes), mut right| {
let orig_right = right;
assert_eq!(
unsafe {
compiler_builtins::aarch64_outline_atomics::$name::$name(left, &mut right)
},
orig_right
);
assert_eq!(left, right);
});
crate::run_fuzz_tests_with_lse_variants(
10000,
|left: super::int_ty!($bytes), mut right| {
let orig_right = right;
assert_eq!(
unsafe {
compiler_builtins::aarch64_outline_atomics::$name::$name(
left, &mut right,
)
},
orig_right
);
assert_eq!(left, right);
},
);
}
}
}
@@ -80,7 +128,7 @@ mod $mod {
($_ordering:ident, $bytes:tt, $name:ident) => {
#[test]
fn $name() {
builtins_test::fuzz_2(10000, |old, val| {
crate::run_fuzz_tests_with_lse_variants(10000, |old, val| {
let mut target = old;
let op: fn(super::int_ty!($bytes), super::int_ty!($bytes)) -> _ = $($op)*;
let expected = op(old, val);
@@ -98,7 +146,6 @@ fn $name() {
test_op!(clr, |left, right| left & !right);
test_op!(xor, std::ops::BitXor::bitxor);
test_op!(or, std::ops::BitOr::bitor);
use compiler_builtins::{foreach_bytes, foreach_ordering};
compiler_builtins::foreach_cas!(cas::test);
compiler_builtins::foreach_cas16!(test_cas16);
compiler_builtins::foreach_swp!(swap::test);
@@ -1,6 +1,6 @@
#![cfg_attr(f16_enabled, feature(f16))]
#![cfg_attr(f128_enabled, feature(f128))]
#![allow(unused_macros)]
#![allow(unused_macros, unused_features)]
use builtins_test::*;
@@ -6,4 +6,4 @@
export LIBM_SEED=benchesbenchesbenchesbencheswoo!
cargo bench --package libm-test \
--no-default-features \
--features short-benchmarks,build-musl,libm/force-soft-floats
--features walltime,short-benchmarks,build-musl,libm/force-soft-floats
+61 -13
View File
@@ -11,7 +11,7 @@ import pprint
import re
import subprocess as sp
import sys
from dataclasses import dataclass
from dataclasses import dataclass, field
from functools import cache
from glob import glob
from inspect import cleandoc
@@ -19,8 +19,7 @@ from os import getenv
from pathlib import Path
from typing import TypedDict, Self
USAGE = cleandoc(
"""
USAGE = cleandoc("""
usage:
./ci/ci-util.py <COMMAND> [flags]
@@ -44,8 +43,7 @@ USAGE = cleandoc(
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.
"""
)
""")
REPO_ROOT = Path(__file__).parent.parent
GIT = ["git", "-C", REPO_ROOT]
@@ -73,17 +71,21 @@ def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
@dataclass(init=False)
@dataclass(kw_only=True)
class PrCfg:
"""Directives that we allow in the commit body to control test behavior.
These are of the form `ci: foo`, at the start of a line.
"""
# The PR body
body: str
# Skip regression checks (must be at the start of a line).
allow_regressions: bool = False
# Don't run extensive tests
skip_extensive: bool = False
# Add these extensive tests to the list
extra_extensive: list[str] = field(default_factory=list, init=False)
# Allow running a large number of extensive tests. If not set, this script
# will error out if a threshold is exceeded in order to avoid accidentally
@@ -101,11 +103,17 @@ class PrCfg:
DIR_SKIP_EXTENSIVE: str = "skip-extensive"
DIR_ALLOW_MANY_EXTENSIVE: str = "allow-many-extensive"
DIR_TEST_LIBM: str = "test-libm"
DIR_EXTRA_EXTENSIVE: str = "extra-extensive"
def __init__(self, body: str):
directives = re.finditer(r"^\s*ci:\s*(?P<dir_name>\S*)", body, re.MULTILINE)
def __post_init__(self):
directives = re.finditer(
r"^\s*ci:\s*(?P<dir_name>[^\s=]*)(?:\s*=\s*(?P<args>.*))?",
self.body,
re.MULTILINE,
)
for dir in directives:
name = dir.group("dir_name")
args = dir.group("args")
if name == self.DIR_ALLOW_REGRESSIONS:
self.allow_regressions = True
elif name == self.DIR_SKIP_EXTENSIVE:
@@ -114,11 +122,18 @@ class PrCfg:
self.allow_many_extensive = True
elif name == self.DIR_TEST_LIBM:
self.always_test_libm = True
elif name == self.DIR_EXTRA_EXTENSIVE:
self.extra_extensive = [x.strip() for x in args.split(",")]
args = None
else:
eprint(f"Found unexpected directive `{name}`")
exit(1)
pprint.pp(self)
if args is not None:
eprint("Found arguments where not expected")
exit(1)
eprint(pprint.pformat(self))
@dataclass
@@ -158,7 +173,7 @@ class PrInfo:
)
pr_json = json.loads(pr_info)
eprint("PR info:", json.dumps(pr_json, indent=4))
return cls(**json.loads(pr_info), cfg=PrCfg(pr_json["body"]))
return cls(**pr_json, cfg=PrCfg(body=pr_json["body"]))
class FunctionDef(TypedDict):
@@ -276,29 +291,35 @@ class Context:
skip_tests = False
error_on_many_tests = False
extra_tests = {}
pr = PrInfo.from_env()
if pr is not None:
skip_tests = pr.cfg.skip_extensive
error_on_many_tests = not pr.cfg.allow_many_extensive
for fn_name in pr.cfg.extra_extensive:
extra_tests.setdefault(base_name(fn_name)[1], []).append(fn_name)
if skip_tests:
eprint("Skipping all extensive tests")
changed = self.changed_routines()
eprint(f"Changed: {changed}")
matrix = []
total_to_test = 0
# Figure out which extensive tests need to run
for ty in TYPES:
ty_changed = changed.get(ty, [])
ty_to_test = [] if skip_tests else ty_changed
ty_to_test = [] if skip_tests else ty_changed.copy()
ty_to_test.extend(extra_tests.get(ty, []))
total_to_test += len(ty_to_test)
item = {
"ty": ty,
"changed": ",".join(ty_changed),
"to_test": ",".join(ty_to_test),
"changed": ",".join(sorted(set(ty_changed))),
"to_test": ",".join(sorted(set(ty_to_test))),
}
matrix.append(item)
@@ -319,6 +340,33 @@ class Context:
exit(1)
def base_name(name: str) -> tuple[str, str]:
"""Return the basename and type from a full function name. Keep in sync with Rust's
`fn base_name`.
"""
known_mappings = [
("erff", ("erf", "f32")),
("erf", ("erf", "f64")),
("modff", ("modf", "f32")),
("modf", ("modf", "f64")),
("lgammaf_r", ("lgamma_r", "f32")),
("lgamma_r", ("lgamma_r", "f64")),
]
found = next((base for (full, base) in known_mappings if full == name), None)
if found is not None:
return found
if name.endswith("f"):
return (name.rstrip("f"), "f32")
elif name.endswith("f16"):
return (name.rstrip("f16"), "f16")
elif name.endswith("f128"):
return (name.rstrip("f128"), "f128")
return (name, "f64")
def locate_baseline(flags: list[str]) -> None:
"""Find the most recent baseline from CI, download it if specified.
+5 -1
View File
@@ -14,5 +14,9 @@ targets=(
)
for target in "${targets[@]}"; do
# Only run the `mem` tests to avoid this taking too long.
cargo miri test --manifest-path builtins-test/Cargo.toml --features no-asm --target "$target" -- mem
cargo miri test \
--manifest-path builtins-test/Cargo.toml \
--features no-asm \
--target "$target" \
-- mem
done
+11 -8
View File
@@ -48,21 +48,24 @@ fi
# build with the arguments we provide it, then validates the built artifacts.
SYMCHECK_TEST_TARGET="$target" cargo test -p symbol-check --release
symcheck=(cargo run -p symbol-check --release)
symcheck+=(-- build-and-check)
symcheck+=(-- --build-and-check --target "$target")
"${symcheck[@]}" "$target" -- -p compiler_builtins
"${symcheck[@]}" "$target" -- -p compiler_builtins --release
"${symcheck[@]}" "$target" -- -p compiler_builtins --features c
"${symcheck[@]}" "$target" -- -p compiler_builtins --features c --release
"${symcheck[@]}" "$target" -- -p compiler_builtins --features no-asm
"${symcheck[@]}" "$target" -- -p compiler_builtins --features no-asm --release
# Executable section checks are meaningless on no-std targets
[[ "$target" == *"-none"* ]] && symcheck+=(--no-os)
"${symcheck[@]}" -- -p compiler_builtins
"${symcheck[@]}" -- -p compiler_builtins --release
"${symcheck[@]}" -- -p compiler_builtins --features c
"${symcheck[@]}" -- -p compiler_builtins --features c --release
"${symcheck[@]}" -- -p compiler_builtins --features no-asm
"${symcheck[@]}" -- -p compiler_builtins --features no-asm --release
run_intrinsics_test() {
build_args=(--verbose --manifest-path builtins-test-intrinsics/Cargo.toml)
build_args+=("$@")
# symcheck also checks the results of builtins-test-intrinsics
"${symcheck[@]}" "$target" -- "${build_args[@]}"
"${symcheck[@]}" -- "${build_args[@]}"
# FIXME: we get access violations on Windows, our entrypoint may need to
# be tweaked.
@@ -22,7 +22,7 @@ See `build.rs` for details.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
See [CONTRIBUTING.md](../CONTRIBUTING.md).
## Progress
@@ -1,7 +1,3 @@
#![allow(unused_imports)]
use core::intrinsics;
intrinsics! {
#[unsafe(naked)]
#[cfg(any(all(windows, target_env = "gnu"), target_os = "uefi"))]
@@ -34,14 +34,27 @@ pub extern "C" fn __rust_enable_lse() {
}
}
/// Function to enable/disable LSE. To be used only for testing purposes.
#[cfg(feature = "mangled-names")]
pub unsafe fn set_have_lse_atomics(has_lse: bool) {
let lse_flag = if has_lse { 1 } else { 0 };
HAVE_LSE_ATOMICS.store(lse_flag, Ordering::Relaxed);
}
/// Function to obtain whether LSE is enabled or not. To be used only for testing purposes.
#[cfg(feature = "mangled-names")]
pub fn get_have_lse_atomics() -> bool {
HAVE_LSE_ATOMICS.load(Ordering::Relaxed) != 0
}
/// Translate a byte size to a Rust type.
#[rustfmt::skip]
macro_rules! int_ty {
(1) => { i8 };
(2) => { i16 };
(4) => { i32 };
(8) => { i64 };
(16) => { i128 };
(1) => { u8 };
(2) => { u16 };
(4) => { u32 };
(8) => { u64 };
(16) => { u128 };
}
/// Given a byte size and a register number, return a register of the appropriate size.
@@ -135,18 +148,73 @@ macro_rules! stxp {
};
}
// The AArch64 assembly syntax for relocation specifiers
// when accessing symbols changes depending on the target executable format.
// In ELF (used in Linux), we have a prefix notation surrounded by colons (:specifier:sym),
// while in Mach-O object files (used in MacOS), a postfix notation is used (sym@specifier).
/// AArch64 ELF position-independent addressing:
///
/// adrp xN, symbol
/// add xN, xN, :lo12:symbol
///
/// The :lo12: modifier selects the low 12 bits of the symbol address
/// and emits an ELF relocation such as R_AARCH64_ADD_ABS_LO12_NC.
///
/// Defined by the AArch64 ELF psABI.
/// See: <https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst#static-miscellaneous-relocations>.
#[cfg(not(target_vendor = "apple"))]
macro_rules! sym {
($sym:literal) => {
$sym
};
}
#[cfg(not(target_vendor = "apple"))]
macro_rules! sym_off {
($sym:literal) => {
concat!(":lo12:", $sym)
};
}
/// Mach-O ARM64 relocation types:
/// ARM64_RELOC_PAGE21
/// ARM64_RELOC_PAGEOFF12
///
/// These relocations implement the @PAGE / @PAGEOFF split used by
/// adrp + add sequences on Apple platforms.
///
/// adrp xN, symbol@PAGE -> ARM64_RELOC_PAGE21
/// add xN, xN, symbol@PAGEOFF -> ARM64_RELOC_PAGEOFF12
///
/// Relocation types defined by Apple in XNU: <mach-o/arm64/reloc.h>.
/// See: <https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/EXTERNAL_HEADERS/mach-o/arm64/reloc.h>.
#[cfg(target_vendor = "apple")]
macro_rules! sym {
($sym:literal) => {
concat!($sym, "@PAGE")
};
}
#[cfg(target_vendor = "apple")]
macro_rules! sym_off {
($sym:literal) => {
concat!($sym, "@PAGEOFF")
};
}
// If supported, perform the requested LSE op and return, or fallthrough.
macro_rules! try_lse_op {
($op: literal, $ordering:ident, $bytes:tt, $($reg:literal,)* [ $mem:ident ] ) => {
concat!(
".arch_extension lse; ",
"adrp x16, {have_lse}; ",
"ldrb w16, [x16, :lo12:{have_lse}]; ",
"cbz w16, 8f; ",
".arch_extension lse\n",
concat!("adrp x16, ", sym!("{have_lse}"), "\n"),
concat!("ldrb w16, [x16, ", sym_off!("{have_lse}"), "]\n"),
"cbz w16, 8f\n",
// LSE_OP s(reg),* [$mem]
concat!(lse!($op, $ordering, $bytes), $( " ", reg!($bytes, $reg), ", " ,)* "[", stringify!($mem), "]; ",),
"ret; ",
"8:"
concat!(lse!($op, $ordering, $bytes), $( " ", reg!($bytes, $reg), ", " ,)* "[", stringify!($mem), "]\n",),
"ret
8:"
)
};
}
@@ -203,15 +271,15 @@ macro_rules! compare_and_swap {
};
}
// i128 uses a completely different impl, so it has its own macro.
macro_rules! compare_and_swap_i128 {
// u128 uses a completely different impl, so it has its own macro.
macro_rules! compare_and_swap_u128 {
($ordering:ident, $name:ident) => {
intrinsics! {
#[maybe_use_optimized_c_shim]
#[unsafe(naked)]
pub unsafe extern "C" fn $name (
expected: i128, desired: i128, ptr: *mut i128
) -> i128 {
expected: u128, desired: u128, ptr: *mut u128
) -> u128 {
core::arch::naked_asm! {
// CASP x0, x1, x2, x3, [x4]; if LSE supported.
try_lse_op!("cas", $ordering, 16, 0, 1, 2, 3, [x4]),
@@ -391,7 +459,7 @@ macro_rules! foreach_ldset {
}
foreach_cas!(compare_and_swap);
foreach_cas16!(compare_and_swap_i128);
foreach_cas16!(compare_and_swap_u128);
foreach_swp!(swap);
foreach_ldadd!(add);
foreach_ldclr!(and);
@@ -7,7 +7,6 @@
#![feature(compiler_builtins)]
#![feature(core_intrinsics)]
#![feature(linkage)]
#![feature(naked_functions)]
#![feature(repr_simd)]
#![feature(macro_metavar_expr_concat)]
#![feature(rustc_attrs)]
@@ -57,7 +56,12 @@
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
pub mod aarch64;
#[cfg(all(target_arch = "aarch64", target_feature = "outline-atomics"))]
// Note that we enable the module on "mangled-names" because that is the default feature
// in the builtins-test tests. So this is a way of enabling the module during testing.
#[cfg(all(
target_arch = "aarch64",
any(target_feature = "outline-atomics", feature = "mangled-names")
))]
pub mod aarch64_outline_atomics;
#[cfg(target_arch = "avr")]
@@ -28,8 +28,10 @@ pub mod full_availability {
fn fdimf16(x: f16, y: f16) -> f16;
fn floorf16(x: f16) -> f16;
fn fmaxf16(x: f16, y: f16) -> f16;
fn fmaximum_numf16(x: f16, y: f16) -> f16;
fn fmaximumf16(x: f16, y: f16) -> f16;
fn fminf16(x: f16, y: f16) -> f16;
fn fminimum_numf16(x: f16, y: f16) -> f16;
fn fminimumf16(x: f16, y: f16) -> f16;
fn fmodf16(x: f16, y: f16) -> f16;
fn rintf16(x: f16) -> f16;
@@ -81,8 +83,12 @@ pub mod full_availability {
// however, so we still provide a fallback.
libm_intrinsics! {
fn fmaximum(x: f64, y: f64) -> f64;
fn fmaximum_num(x: f64, y: f64) -> f64;
fn fmaximum_numf(x: f32, y: f32) -> f32;
fn fmaximumf(x: f32, y: f32) -> f32;
fn fminimum(x: f64, y: f64) -> f64;
fn fminimum_num(x: f64, y: f64) -> f64;
fn fminimum_numf(x: f32, y: f32) -> f32;
fn fminimumf(x: f32, y: f32) -> f32;
fn roundeven(x: f64) -> f64;
fn roundevenf(x: f32) -> f32;
@@ -97,8 +103,10 @@ pub mod full_availability {
fn floorf128(x: f128) -> f128;
fn fmaf128(x: f128, y: f128, z: f128) -> f128;
fn fmaxf128(x: f128, y: f128) -> f128;
fn fmaximum_numf128(x: f128, y: f128) -> f128;
fn fmaximumf128(x: f128, y: f128) -> f128;
fn fminf128(x: f128, y: f128) -> f128;
fn fminimum_numf128(x: f128, y: f128) -> f128;
fn fminimumf128(x: f128, y: f128) -> f128;
fn fmodf128(x: f128, y: f128) -> f128;
fn rintf128(x: f128) -> f128;
@@ -16,7 +16,8 @@
// crate doing wrapping pointer arithmetic with a method that must not wrap won't be the problem if
// something does go wrong at runtime.
use core::ffi::c_int;
use core::intrinsics::likely;
use crate::support::cold_path;
const WORD_SIZE: usize = core::mem::size_of::<usize>();
const WORD_MASK: usize = WORD_SIZE - 1;
@@ -209,9 +210,10 @@ unsafe fn copy_forward_misaligned_words(dest: *mut u8, src: *const u8, n: usize)
let n_words = n & !WORD_MASK;
let src_misalignment = src as usize & WORD_MASK;
if likely(src_misalignment == 0) {
if src_misalignment == 0 {
copy_forward_aligned_words(dest, src, n_words);
} else {
cold_path();
copy_forward_misaligned_words(dest, src, n_words);
}
dest = dest.wrapping_add(n_words);
@@ -327,9 +329,10 @@ unsafe fn copy_backward_misaligned_words(dest: *mut u8, src: *const u8, n: usize
let n_words = n & !WORD_MASK;
let src_misalignment = src as usize & WORD_MASK;
if likely(src_misalignment == 0) {
if src_misalignment == 0 {
copy_backward_aligned_words(dest, src, n_words);
} else {
cold_path();
copy_backward_misaligned_words(dest, src, n_words);
}
dest = dest.wrapping_sub(n_words);
@@ -368,7 +371,7 @@ pub unsafe fn set_bytes_words(s: *mut u8, c: u8, n: usize) {
}
}
if likely(n >= WORD_COPY_THRESHOLD) {
if n >= WORD_COPY_THRESHOLD {
// Align s
// Because of n >= 2 * WORD_SIZE, dst_misalignment < n
let misalignment = (s as usize).wrapping_neg() & WORD_MASK;
@@ -380,6 +383,8 @@ pub unsafe fn set_bytes_words(s: *mut u8, c: u8, n: usize) {
set_bytes_words(s, c, n_words);
s = s.wrapping_add(n_words);
n -= n_words;
} else {
cold_path();
}
set_bytes_bytes(s, c, n);
}
@@ -1,7 +1,3 @@
#![allow(unused_imports)]
use core::intrinsics;
// NOTE These functions are implemented using assembly because they use a custom
// calling convention which can't be implemented using a normal Rust function
@@ -1,7 +1,3 @@
#![allow(unused_imports)]
use core::intrinsics;
// NOTE These functions are implemented using assembly because they use a custom
// calling convention which can't be implemented using a normal Rust function
@@ -279,6 +279,17 @@ struct NestedOp {
fn_list: &["fmaf128"],
public: true,
},
NestedOp {
// `(f16) -> i32`
float_ty: FloatTy::F16,
rust_sig: Signature {
args: &[Ty::F16],
returns: &[Ty::I32],
},
c_sig: None,
fn_list: &["ilogbf16"],
public: true,
},
NestedOp {
// `(f32) -> i32`
float_ty: FloatTy::F32,
@@ -301,6 +312,17 @@ struct NestedOp {
fn_list: &["ilogb"],
public: true,
},
NestedOp {
// `(f128) -> i32`
float_ty: FloatTy::F128,
rust_sig: Signature {
args: &[Ty::F128],
returns: &[Ty::I32],
},
c_sig: None,
fn_list: &["ilogbf128"],
public: true,
},
NestedOp {
// `(i32, f32) -> f32`
float_ty: FloatTy::F32,
@@ -395,6 +417,20 @@ struct NestedOp {
fn_list: &["modf"],
public: true,
},
NestedOp {
// `(f16, &mut c_int) -> f16` as `(f16) -> (f16, i32)`
float_ty: FloatTy::F16,
rust_sig: Signature {
args: &[Ty::F16],
returns: &[Ty::F16, Ty::I32],
},
c_sig: Some(Signature {
args: &[Ty::F16, Ty::MutCInt],
returns: &[Ty::F16],
}),
fn_list: &["frexpf16"],
public: true,
},
NestedOp {
// `(f32, &mut c_int) -> f32` as `(f32) -> (f32, i32)`
float_ty: FloatTy::F32,
@@ -423,6 +459,20 @@ struct NestedOp {
fn_list: &["frexp", "lgamma_r"],
public: true,
},
NestedOp {
// `(f128, &mut c_int) -> f128` as `(f128) -> (f128, i32)`
float_ty: FloatTy::F128,
rust_sig: Signature {
args: &[Ty::F128],
returns: &[Ty::F128, Ty::I32],
},
c_sig: Some(Signature {
args: &[Ty::F128, Ty::MutCInt],
returns: &[Ty::F128],
}),
fn_list: &["frexpf128"],
public: true,
},
NestedOp {
// `(f32, f32, &mut c_int) -> f32` as `(f32, f32) -> (f32, i32)`
float_ty: FloatTy::F32,
@@ -1,3 +1,4 @@
#![allow(unused_features)]
#![feature(f16)]
#![feature(f128)]
// `STATUS_DLL_NOT_FOUND` on i686 MinGW, not worth looking into.
@@ -5,6 +5,7 @@ edition = "2024"
publish = false
[dependencies]
getopts.workspace = true
object.workspace = true
regex.workspace = true
serde_json.workspace = true
@@ -5,75 +5,100 @@
//! actual target is cross compiled.
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fs;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::process::{Command, Stdio, exit};
use std::sync::LazyLock;
use std::{env, fs};
use object::read::archive::ArchiveFile;
use object::{
File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, Symbol, SymbolKind,
SymbolScope,
Architecture, BinaryFormat, Endianness, File as ObjFile, Object, ObjectSection, ObjectSymbol,
Result as ObjResult, SectionFlags, Symbol, SymbolKind, SymbolScope, U32, elf,
};
use regex::Regex;
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 GNU_STACK: &str = ".note.GNU-stack";
const USAGE: &str = "Usage:
symbol-check build-and-check [TARGET] -- CARGO_BUILD_ARGS ...
Cargo will get invoked with `CARGO_ARGS` and the specified target. All output
`compiler_builtins*.rlib` files will be checked.
If TARGET is not specified, the host target is used.
check PATHS ...
Run the same checks on the given set of paths, without invoking Cargo. Paths
may be either archives or object files.
symbol-check --build-and-check [--target TARGET] [--no-os] -- CARGO_BUILD_ARGS ...
symbol-check --check PATHS ...\
";
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<_>>();
let mut opts = getopts::Options::new();
match &args_ref[1..] {
["build-and-check", target, "--", args @ ..] if !args.is_empty() => {
run_build_and_check(target, args);
}
["build-and-check", "--", args @ ..] if !args.is_empty() => {
run_build_and_check(env!("HOST"), args);
}
["check", paths @ ..] if !paths.is_empty() => {
check_paths(paths);
}
_ => {
println!("{USAGE}");
std::process::exit(1);
}
// Ideally these would be subcommands but that isn't supported.
opts.optflag("h", "help", "Print this help message");
opts.optflag(
"",
"build-and-check",
"Cargo will get invoked with `CARGO_BUILD_ARGS` and the specified target. All output \
`compiler_builtins*.rlib` files will be checked.",
);
opts.optopt(
"",
"target",
"Set the target for build-and-check. Falls back to the host target otherwise.",
"TARGET",
);
opts.optflag(
"",
"check",
"Run checks on the given set of paths, without invoking Cargo. Paths \
may be either archives or object files.",
);
opts.optflag(
"",
"no-os",
"The binaries will not be checked for executable stacks. Used for embedded targets which \
don't set `.note.GNU-stack` since there is no protection.",
);
opts.optflag("", "no-visibility", "Don't check visibility.");
let print_usage_and_exit = |code: i32| -> ! {
eprintln!("{}", opts.usage(USAGE));
exit(code);
};
let m = opts.parse(std::env::args().skip(1)).unwrap_or_else(|e| {
eprintln!("{e}");
print_usage_and_exit(1);
});
if m.opt_present("help") {
print_usage_and_exit(0);
}
}
fn run_build_and_check(target: &str, args: &[&str]) {
// Make sure `--target` isn't passed to avoid confusion (since it should be
// proivded only once, positionally).
for arg in args {
let no_os_target = m.opt_present("no-os");
let check_visibility = !m.opt_present("no-visibility");
let free_args = m.free.iter().map(String::as_str).collect::<Vec<_>>();
for arg in &free_args {
assert!(
!arg.contains("--target"),
"target must be passed positionally. {USAGE}"
"target must be passed to symbol-check"
);
}
let paths = exec_cargo_with_args(target, args);
check_paths(&paths);
if m.opt_present("build-and-check") {
let target = m.opt_str("target").unwrap_or(env!("HOST").to_string());
let paths = exec_cargo_with_args(&target, &free_args);
check_paths(&paths, no_os_target, check_visibility);
} else if m.opt_present("check") {
if free_args.is_empty() {
print_usage_and_exit(1);
}
check_paths(&free_args, no_os_target, check_visibility);
} else {
print_usage_and_exit(1);
}
}
fn check_paths<P: AsRef<Path>>(paths: &[P]) {
fn check_paths<P: AsRef<Path>>(paths: &[P], no_os_target: bool, check_visibility: bool) {
for path in paths {
let path = path.as_ref();
println!("Checking {}", path.display());
@@ -81,6 +106,10 @@ fn check_paths<P: AsRef<Path>>(paths: &[P]) {
verify_no_duplicates(&archive);
verify_core_symbols(&archive);
verify_no_exec_stack(&archive, no_os_target);
if check_visibility {
verify_hidden_visibility(&archive);
}
}
}
@@ -244,7 +273,9 @@ fn verify_no_duplicates(archive: &BinFile) {
found_any = true;
});
assert!(found_any, "no symbols found");
if archive.has_symbol_tables() {
assert!(found_any, "no symbols found");
}
if !dups.is_empty() {
let count = dups.iter().map(|x| &x.name).collect::<HashSet<_>>().len();
@@ -285,7 +316,9 @@ fn verify_core_symbols(archive: &BinFile) {
}
});
assert!(has_symbols, "no symbols found");
if archive.has_symbol_tables() {
assert!(has_symbols, "no symbols found");
}
// Discard any symbols that are defined somewhere in the archive
undefined.retain(|sym| !defined.contains(&sym.name));
@@ -301,6 +334,209 @@ fn verify_core_symbols(archive: &BinFile) {
println!(" success: no undefined references to core found");
}
/// Check for symbols with default visibility.
fn verify_hidden_visibility(archive: &BinFile) {
let mut visible = Vec::new();
let mut found_any = false;
archive.for_each_symbol(|symbol, obj, member| {
// Only check defined globals.
if !symbol.is_global() || symbol.is_undefined() {
return;
}
let sym = SymInfo::new(&symbol, obj, member);
if sym.scope == SymbolScope::Dynamic {
visible.push(sym);
}
found_any = true
});
if archive.has_symbol_tables() {
assert!(found_any, "no symbols found");
}
if !visible.is_empty() {
visible.sort_unstable_by(|a, b| a.name.cmp(&b.name));
let num = visible.len();
panic!("found {num:#?} visible symbols: {visible:#?}");
}
println!(" success: no visible symbols found");
}
/// Reasons a binary is considered to have an executable stack.
enum ExeStack {
MissingGnuStackSec,
ExeGnuStackSec,
ExePtGnuStack,
}
/// Ensure that the object/archive will not require an executable stack.
fn verify_no_exec_stack(archive: &BinFile, no_os_target: bool) {
if no_os_target {
// We don't really have a good way of knowing whether or not an elf file is for a
// no-os environment so we rely on a CLI arg (note.GNU-stack doesn't get emitted if
// there is no OS to protect the stack).
println!(" skipping check for writeable+executable stack on no-os target");
return;
}
let mut problem_objfiles = Vec::new();
archive.for_each_object(|obj, obj_path| match check_obj_exe_stack(&obj) {
Ok(()) => (),
Err(exe) => problem_objfiles.push((obj_path.to_owned(), exe)),
});
if problem_objfiles.is_empty() {
println!(" success: no writeable+executable stack indicators found");
return;
}
eprintln!("the following object files require an executable stack:");
for (obj, exe) in problem_objfiles {
let reason = match exe {
ExeStack::MissingGnuStackSec => "no .note.GNU-stack section",
ExeStack::ExeGnuStackSec => ".note.GNU-stack section marked SHF_EXECINSTR",
ExeStack::ExePtGnuStack => "PT_GNU_STACK program header marked PF_X",
};
eprintln!(" {obj} ({reason})");
}
exit(1);
}
/// `Err` if the section/flag combination indicates that the object file should be linked with an
/// executable stack.
fn check_obj_exe_stack(obj: &ObjFile) -> Result<(), ExeStack> {
match obj.format() {
BinaryFormat::Elf => check_elf_exe_stack(obj),
// Technically has the `MH_ALLOW_STACK_EXECUTION` flag but I can't get the compiler to
// emit it (`-allow_stack_execute` doesn't seem to work in recent versions).
BinaryFormat::MachO => Ok(()),
// Can't find much information about Windows stack executability.
BinaryFormat::Coff | BinaryFormat::Pe => Ok(()),
// Also not sure about wasm.
BinaryFormat::Wasm => Ok(()),
BinaryFormat::Xcoff | _ => {
unimplemented!("binary format {:?} is not supported", obj.format())
}
}
}
/// Check for an executable stack in elf binaries.
///
/// If the `PT_GNU_STACK` header on a binary is present and marked executable, the binary will
/// have an executable stack (RWE rather than the desired RW). If any object file has the right
/// `.note.GNU-stack` logic, the final binary will get `PT_GNU_STACK`.
///
/// Individual object file logic is as follows, paraphrased from [1]:
///
/// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack
/// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed
/// - Without the section, behavior is target-specific. Historically it usually means an executable
/// stack is required.
///
/// Per [2], it is now deprecated behavior for a missing `.note.GNU-stack` section to imply an
/// executable stack. However, we shouldn't assume that tooling has caught up to this.
///
/// [1]: https://www.man7.org/linux/man-pages/man1/ld.1.html
/// [2]: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d38576a34ec64a1b4500c9277a8e9d0f07e6774>
fn check_elf_exe_stack(obj: &ObjFile) -> Result<(), ExeStack> {
let end = obj.endianness();
// Check for PT_GNU_STACK marked executable
let mut is_obj_exe = false;
let mut found_gnu_stack = false;
let mut check_ph = |p_type: U32<Endianness>, p_flags: U32<Endianness>| {
let ty = p_type.get(end);
let flags = p_flags.get(end);
// Presence of PT_INTERP indicates that this is an executable rather than a standalone
// object file.
if ty == elf::PT_INTERP {
is_obj_exe = true;
}
if ty == elf::PT_GNU_STACK {
assert!(!found_gnu_stack, "multiple PT_GNU_STACK sections");
found_gnu_stack = true;
if flags & elf::PF_X != 0 {
return Err(ExeStack::ExePtGnuStack);
}
}
Ok(())
};
match obj {
ObjFile::Elf32(f) => {
for ph in f.elf_program_headers() {
check_ph(ph.p_type, ph.p_flags)?;
}
}
ObjFile::Elf64(f) => {
for ph in f.elf_program_headers() {
check_ph(ph.p_type, ph.p_flags)?;
}
}
_ => panic!("should only be called with elf objects"),
}
if is_obj_exe {
return Ok(());
}
// The remaining are checks for individual object files, which wind up controlling PT_GNU_STACK
// in the final binary.
let mut gnu_stack_exe = None;
let mut has_exe_sections = false;
for sec in obj.sections() {
let SectionFlags::Elf { sh_flags } = sec.flags() else {
unreachable!("only elf files are being checked");
};
let is_sec_exe = sh_flags & u64::from(elf::SHF_EXECINSTR) != 0;
// If the magic section is present, its exe bit tells us whether or not the object
// file requires an executable stack.
if sec.name().unwrap_or_default() == GNU_STACK {
assert!(gnu_stack_exe.is_none(), "multiple {GNU_STACK} sections");
if is_sec_exe {
gnu_stack_exe = Some(Err(ExeStack::ExeGnuStackSec));
} else {
gnu_stack_exe = Some(Ok(()));
}
}
// Otherwise, just keep track of whether or not we have exeuctable sections
has_exe_sections |= is_sec_exe;
}
// GNU_STACK sets the executability if specified.
if let Some(exe) = gnu_stack_exe {
return exe;
}
// Ignore object files that have no executable sections, like rmeta.
if !has_exe_sections {
return Ok(());
}
// If there is no `.note.GNU-stack` and no executable sections, behavior differs by platform.
match obj.architecture() {
// PPC64 doesn't set `.note.GNU-stack` since GNU nested functions don't need a trampoline,
// <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21098>. From experimentation, it seems
// like this only applies to big endian.
Architecture::PowerPc64 if obj.endianness() == Endianness::Big => Ok(()),
_ => Err(ExeStack::MissingGnuStackSec),
}
}
/// Thin wrapper for owning data used by `object`.
struct BinFile {
path: PathBuf,
@@ -361,4 +597,23 @@ fn for_each_symbol(&self, mut f: impl FnMut(Symbol, &ObjFile, &str)) {
obj.symbols().for_each(|sym| f(sym, &obj, obj_path));
});
}
/// PE executable files don't have the same kind of symbol tables. This isn't a perfectly
/// accurate check, but at least tells us whether we can skip erroring if we don't find any
/// symbols.
fn has_symbol_tables(&self) -> bool {
let mut empty = true;
let mut ret = false;
self.for_each_object(|obj, _obj_path| {
if !matches!(obj.format(), BinaryFormat::Pe) {
// Any non-PE objects should have symbol tables.
ret = true;
}
empty = false;
});
// If empty, assume there should be tables.
empty || ret
}
}
@@ -2,10 +2,10 @@
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::LazyLock;
use assert_cmd::assert::Assert;
use assert_cmd::cargo::cargo_bin_cmd;
use object::BinaryFormat;
use tempfile::tempdir;
trait AssertExt {
@@ -13,6 +13,7 @@ trait AssertExt {
}
impl AssertExt for Assert {
#[track_caller]
fn stderr_contains(self, s: &str) -> Self {
let out = String::from_utf8_lossy(&self.get_output().stderr);
assert!(out.contains(s), "looking for: `{s}`\nout:\n```\n{out}\n```");
@@ -22,18 +23,19 @@ fn stderr_contains(self, s: &str) -> Self {
#[test]
fn test_duplicates() {
let t = TestTarget::from_env();
let dir = tempdir().unwrap();
let dup_out = dir.path().join("dup.o");
let lib_out = dir.path().join("libfoo.rlib");
// For the "bad" file, we need duplicate symbols from different object files in the archive. Do
// this reliably by building an archive and a separate object file then merging them.
rustc_build(&input_dir().join("duplicates.rs"), &lib_out, |cmd| cmd);
rustc_build(&input_dir().join("duplicates.rs"), &dup_out, |cmd| {
t.rustc_build(&input_dir().join("duplicates.rs"), &lib_out, |cmd| cmd);
t.rustc_build(&input_dir().join("duplicates.rs"), &dup_out, |cmd| {
cmd.arg("--emit=obj")
});
let mut ar = cc_build().get_archiver();
let mut ar = t.cc_build().get_archiver();
if ar.get_program().to_string_lossy().contains("lib.exe") {
let mut out_arg = OsString::from("-out:");
@@ -48,10 +50,10 @@ fn test_duplicates() {
.stderr(Stdio::null())
.arg(&lib_out);
}
let status = ar.arg(&dup_out).status().unwrap();
assert!(status.success());
let assert = cargo_bin_cmd!().arg("check").arg(&lib_out).assert();
run(ar.arg(&dup_out));
let assert = t.symcheck_exe().arg(&lib_out).assert();
assert
.failure()
.stderr_contains("duplicate symbols")
@@ -62,10 +64,11 @@ fn test_duplicates() {
#[test]
fn test_core_symbols() {
let t = TestTarget::from_env();
let dir = tempdir().unwrap();
let lib_out = dir.path().join("libfoo.rlib");
rustc_build(&input_dir().join("core_symbols.rs"), &lib_out, |cmd| cmd);
let assert = cargo_bin_cmd!().arg("check").arg(&lib_out).assert();
t.rustc_build(&input_dir().join("core_symbols.rs"), &lib_out, |cmd| cmd);
let assert = t.symcheck_exe().arg(&lib_out).assert();
assert
.failure()
.stderr_contains("found 1 undefined symbols from core")
@@ -73,40 +76,160 @@ fn test_core_symbols() {
}
#[test]
fn test_good() {
fn test_visible_symbols() {
let t = TestTarget::from_env();
if t.is_windows() {
eprintln!("windows does not have visibility, skipping");
return;
}
let dir = tempdir().unwrap();
let lib_out = dir.path().join("libfoo.rlib");
rustc_build(&input_dir().join("good.rs"), &lib_out, |cmd| cmd);
let assert = cargo_bin_cmd!().arg("check").arg(&lib_out).assert();
t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd);
let assert = t.symcheck_exe().arg(&lib_out).assert();
assert.failure().stderr_contains("found 1 visible symbols"); // good is visible.
}
mod exe_stack {
use super::*;
/// Check with an object that has no `.note.GNU-stack` section, indicating platform-default stack
/// writeability (usually enabled).
#[test]
fn test_missing_gnu_stack_section() {
let t = TestTarget::from_env();
if t.is_msvc() {
// Can't easily build asm via cc with cl.exe / masm.exe
eprintln!("assembly on windows, skipping");
return;
}
let dir = tempdir().unwrap();
let src = input_dir().join("missing_gnu_stack_section.S");
let objs = t.cc_build().file(src).out_dir(&dir).compile_intermediates();
let [obj] = objs.as_slice() else { panic!() };
let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert();
if t.is_ppc64be() || t.no_os() || t.binary_obj_format() != BinaryFormat::Elf {
// Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS, and non-elf
// targets don't use `.note.GNU-stack`.
assert.success();
return;
}
assert
.failure()
.stderr_contains("the following object files require an executable stack")
.stderr_contains("missing_gnu_stack_section.o (no .note.GNU-stack section)");
}
/// Check with an object that has a `.note.GNU-stack` section with the executable flag set.
#[test]
fn test_exe_gnu_stack_section() {
let t = TestTarget::from_env();
let mut build = t.cc_build();
if !build.get_compiler().is_like_gnu() || t.is_windows() {
eprintln!("unsupported compiler for nested functions, skipping");
return;
}
let dir = tempdir().unwrap();
let objs = build
.file(input_dir().join("has_exe_gnu_stack_section.c"))
.out_dir(&dir)
.compile_intermediates();
let [obj] = objs.as_slice() else { panic!() };
let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert();
if t.is_ppc64be() || t.no_os() {
// Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS.
assert.success();
return;
}
assert
.failure()
.stderr_contains("the following object files require an executable stack")
.stderr_contains(
"has_exe_gnu_stack_section.o (.note.GNU-stack section marked SHF_EXECINSTR)",
);
}
/// Check a final binary with `PT_GNU_STACK`.
#[test]
fn test_execstack_bin() {
let t = TestTarget::from_env();
if t.binary_obj_format() != BinaryFormat::Elf || !t.supports_executables() {
// Mac's Clang rejects `-z execstack`. `-allow_stack_execute` should work per the ld
// manpage, at least on x86, but it doesn't seem to., not relevant without an OS.
eprintln!("non-elf or no-executable target, skipping");
return;
}
let dir = tempdir().unwrap();
let out = dir.path().join("execstack.out");
let mut cmd = t.cc_build().get_compiler().to_command();
t.set_bin_out_path(&mut cmd, &out);
run(cmd
.arg("-z")
.arg("execstack")
.arg(input_dir().join("good_bin.c")));
let assert = t.symcheck_exe().arg(&out).assert();
assert
.failure()
.stderr_contains("the following object files require an executable stack")
.stderr_contains("execstack.out (PT_GNU_STACK program header marked PF_X)");
}
}
#[test]
fn test_good_lib() {
let t = TestTarget::from_env();
let dir = tempdir().unwrap();
let lib_out = dir.path().join("libfoo.rlib");
t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd);
let assert = t
.symcheck_exe()
.arg(&lib_out)
.arg("--no-visibility")
.assert();
assert.success();
}
/// Build i -> o with optional additional configuration.
fn rustc_build(i: &Path, o: &Path, mut f: impl FnMut(&mut Command) -> &mut Command) {
let mut cmd = Command::new("rustc");
cmd.arg(i)
.arg("--target")
.arg(target())
.arg("--crate-type=lib")
.arg("-o")
.arg(o);
f(&mut cmd);
let status = cmd.status().unwrap();
assert!(status.success());
#[test]
fn test_good_bin() {
let t = TestTarget::from_env();
// Nothing to test if we can't build a binary.
if !t.supports_executables() {
eprintln!("no-exe target, skipping");
return;
}
let dir = tempdir().unwrap();
let out = dir.path().join("good_bin.out");
let mut cmd = t.cc_build().get_compiler().to_command();
t.set_bin_out_path(&mut cmd, &out);
run(cmd.arg(input_dir().join("good_bin.c")));
let assert = t.symcheck_exe().arg(&out).arg("--no-visibility").assert();
assert.success();
}
/// Configure `cc` with the host and target.
fn cc_build() -> cc::Build {
let mut b = cc::Build::new();
b.host(env!("HOST")).target(&target());
b
/// Since symcheck is a hostprog, the target we want to build and test symcheck for may not be the
/// same as the host target.
struct TestTarget {
triple: String,
}
/// Symcheck runs on the host but we want to verify that we find issues on all targets, so
/// the cross target may be specified.
fn target() -> String {
static TARGET: LazyLock<String> = LazyLock::new(|| {
let target = match env::var("SYMCHECK_TEST_TARGET") {
impl TestTarget {
fn from_env() -> Self {
let triple = match env::var("SYMCHECK_TEST_TARGET") {
Ok(t) => t,
// Require on CI so we don't accidentally always test the native target
_ if env::var("CI").is_ok() => panic!("SYMCHECK_TEST_TARGET must be set in CI"),
@@ -114,13 +237,108 @@ fn target() -> String {
Err(_) => env!("HOST").to_string(),
};
println!("using target {target}");
target
});
println!("using target {triple}");
Self { triple }
}
TARGET.clone()
/// Build i -> o with optional additional configuration.
fn rustc_build(&self, i: &Path, o: &Path, mut f: impl FnMut(&mut Command) -> &mut Command) {
let mut cmd = Command::new("rustc");
cmd.arg(i)
.arg("--target")
.arg(&self.triple)
.arg("--crate-type=lib")
.arg("-o")
.arg(o);
f(&mut cmd);
run(&mut cmd);
}
/// Configure `cc` with the host and target.
fn cc_build(&self) -> cc::Build {
let mut b = cc::Build::new();
b.host(env!("HOST"))
.target(&self.triple)
.opt_level(0)
.cargo_debug(true)
.cargo_metadata(false);
b
}
fn symcheck_exe(&self) -> assert_cmd::Command {
let mut cmd = cargo_bin_cmd!();
cmd.arg("--check");
if self.no_os() {
cmd.arg("--no-os");
}
cmd
}
/// MSVC requires different flags for setting output path, account for that here.
fn set_bin_out_path<'a>(&self, cmd: &'a mut Command, out: &Path) -> &'a mut Command {
if self.cc_build().get_compiler().is_like_msvc() {
let mut exe_arg = OsString::from("/Fe");
let mut obj_arg = OsString::from("/Fo");
exe_arg.push(out);
obj_arg.push(out.with_extension("o"));
cmd.arg(exe_arg).arg(obj_arg)
} else {
cmd.arg("-o").arg(out)
}
}
/// Based on `rustc_target`.
fn binary_obj_format(&self) -> BinaryFormat {
let t = &self.triple;
if t.contains("-windows-") || t.contains("-cygwin") {
// Coff for libraries, PE for executables.
BinaryFormat::Coff
} else if t.starts_with("wasm") {
BinaryFormat::Wasm
} else if t.contains("-aix") {
BinaryFormat::Xcoff
} else if t.contains("-apple-") {
BinaryFormat::MachO
} else {
BinaryFormat::Elf
}
}
fn is_windows(&self) -> bool {
self.triple.contains("-windows-")
}
fn is_msvc(&self) -> bool {
self.triple.contains("-windows-msvc")
}
fn is_ppc64be(&self) -> bool {
self.triple.starts_with("powerpc64-")
}
/// True if the target needs `--no-os` passed to symcheck.
fn no_os(&self) -> bool {
self.triple.contains("-none")
}
/// True if the target supports (easily) building to a final executable.
fn supports_executables(&self) -> bool {
// Technically i686-pc-windows-gnu should work but it has nontrivial setup in CI.
!(self.no_os()
|| self.triple == "wasm32-unknown-unknown"
|| self.triple == "i686-pc-windows-gnu")
}
}
fn input_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/input")
}
#[track_caller]
fn run(cmd: &mut Command) {
eprintln!("+ {cmd:?}");
let out = cmd.output().unwrap();
println!("{}", String::from_utf8_lossy(&out.stdout));
eprintln!("{}", String::from_utf8_lossy(&out.stderr));
assert!(out.status.success(), "{:?}", out.status);
}
@@ -0,0 +1,3 @@
/* empty main used to test binaries with compiler options */
int main() {}
@@ -0,0 +1,16 @@
/* A file that requires an executable stack and thus will have a
* `.note.GNU-stack` section with the executable bit set.
*
* GNU nested functions are the only way I could find to force an explicitly
* executable stack. Supported by GCC only, not Clang.
*/
void intermediate(void (*)(int, int), int);
void hack(int *array, int size) {
void store (int index, int value) {
array[index] = value;
}
intermediate(store, size);
}
@@ -0,0 +1,19 @@
/* Create an object file with no `.note.GNU-stack` section.
*
* Assembly files do not get that section, meaning platform-default stack
* executability is implied (usually yes on Linux).
*/
.global func
#ifdef __wasm__
.functype func () -> ()
.type func, @function
#endif
func:
nop
#ifdef __wasm__
end_function
#endif
@@ -6,6 +6,7 @@ publish = false
license = "MIT OR Apache-2.0"
[dependencies]
cfg-if.workspace = true
libm.workspace = true
libm-macros.workspace = true
libm-test.workspace = true
@@ -8,10 +8,11 @@
use std::num::ParseIntError;
use std::str::FromStr;
use libm::support::{Hexf, hf32, hf64};
use cfg_if::cfg_if;
use libm::support::{Float, Hexf, hf32, hf64};
#[cfg(feature = "build-mpfr")]
use libm_test::mpfloat::MpOp;
use libm_test::{MathOp, TupleCall};
use libm_test::{Hex, MathOp, TupleCall};
#[cfg(feature = "build-mpfr")]
use rug::az::{self, Az};
@@ -22,10 +23,16 @@
SUBCOMMAND:
eval <BASIS> <OP> inputs...
x <BASIS> <OP> inputs...
Evaulate the expression with a given basis. This can be useful for
running routines with a debugger, or quickly checking input. Examples:
* eval musl sinf 1.234 # print the results of musl sinf(1.234f32)
* eval mpfr pow 1.234 2.432 # print the results of mpfr pow(1.234, 2.432)
print inputs...
p inputs...
For each input, print it in different formats with various floating
point properties (normal, infinite, etc).
";
fn main() {
@@ -33,7 +40,8 @@ fn main() {
let str_args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
match &str_args.as_slice()[1..] {
["eval", basis, op, inputs @ ..] => do_eval(basis, op, inputs),
["eval" | "x", basis, op, inputs @ ..] => do_eval(basis, op, inputs),
["print" | "p", inputs @ ..] => do_classify(inputs),
_ => {
println!("{USAGE}\nunrecognized input `{str_args:?}`");
std::process::exit(1);
@@ -106,6 +114,66 @@ fn do_eval(basis: &str, op: &str, inputs: &[&str]) {
panic!("no operation matching {op}");
}
/// Print basic float information to stdout.
fn do_classify(inputs: &[&str]) {
for s in inputs {
if let Some(s) = s.strip_suffix("f16") {
cfg_if! {
if #[cfg(f16_enabled)] {
let s = s.trim_end_matches("_");
let x: f16 = parse(&[s], 0);
classify_print(x);
continue;
} else {
panic!("parsing this type requires f16 support: `{s}`");
}
}
};
if let Some(s) = s.strip_suffix("f32") {
let s = s.trim_end_matches("_");
let x: f32 = parse(&[s], 0);
classify_print(x);
continue;
} else if let Some(s) = s.strip_suffix("f64") {
let s = s.trim_end_matches("_");
let x: f64 = parse(&[s], 0);
classify_print(x);
continue;
}
if let Some(s) = s.strip_suffix("f128") {
cfg_if! {
if #[cfg(all(f128_enabled, feature = "build-mpfr"))] {
let s = s.trim_end_matches("_");
let x: f128 = parse_rug(&[s], 0);
classify_print(x);
continue;
} else {
panic!("parsing this type requires f128 support and \
the `build-mpfr` feature: `{s}`");
}
}
};
panic!("float type must be specified with a `f*` suffix: `{s}`");
}
}
fn classify_print<F>(x: F)
where
F: Float,
F::Int: Hex,
{
println!("{x:?}");
println!(" hex: {}", Hexf(x));
println!(" bits: {}", x.to_bits().hex());
println!(" nan: {}", x.is_nan());
println!(" inf: {}", x.is_infinite());
println!(" normal: {}", !x.is_subnormal());
println!(" pos: {}", x.is_sign_positive());
println!(" exp: {} {}", x.ex(), x.ex().hex());
println!(" exp unbiased: {}", x.exp_unbiased());
println!(" frac: {} {}", x.frac(), x.frac().hex());
}
/// Parse a tuple from a space-delimited string.
trait ParseTuple {
fn parse(input: &[&str]) -> Self;
@@ -232,11 +300,11 @@ fn parse(_input: &[&str]) -> Self {
};
}
#[cfg(f16_enabled)]
impl_parse_tuple!(f16);
impl_parse_tuple!(f32);
impl_parse_tuple!(f64);
#[cfg(f16_enabled)]
impl_parse_tuple_via_rug!(f16);
#[cfg(f128_enabled)]
impl_parse_tuple_via_rug!(f128);
@@ -560,16 +560,32 @@
},
"frexp": {
"sources": [
"libm/src/math/frexp.rs"
"libm/src/math/frexp.rs",
"libm/src/math/generic/frexp.rs"
],
"type": "f64"
},
"frexpf": {
"sources": [
"libm/src/math/frexpf.rs"
"libm/src/math/frexp.rs",
"libm/src/math/generic/frexp.rs"
],
"type": "f32"
},
"frexpf128": {
"sources": [
"libm/src/math/frexp.rs",
"libm/src/math/generic/frexp.rs"
],
"type": "f128"
},
"frexpf16": {
"sources": [
"libm/src/math/frexp.rs",
"libm/src/math/generic/frexp.rs"
],
"type": "f16"
},
"hypot": {
"sources": [
"libm/src/math/hypot.rs"
@@ -584,16 +600,32 @@
},
"ilogb": {
"sources": [
"libm/src/math/generic/ilogb.rs",
"libm/src/math/ilogb.rs"
],
"type": "f64"
},
"ilogbf": {
"sources": [
"libm/src/math/ilogbf.rs"
"libm/src/math/generic/ilogb.rs",
"libm/src/math/ilogb.rs"
],
"type": "f32"
},
"ilogbf128": {
"sources": [
"libm/src/math/generic/ilogb.rs",
"libm/src/math/ilogb.rs"
],
"type": "f128"
},
"ilogbf16": {
"sources": [
"libm/src/math/generic/ilogb.rs",
"libm/src/math/ilogb.rs"
],
"type": "f16"
},
"j0": {
"sources": [
"libm/src/math/j0.rs"
@@ -84,10 +84,14 @@ fmodf128
fmodf16
frexp
frexpf
frexpf128
frexpf16
hypot
hypotf
ilogb
ilogbf
ilogbf128
ilogbf16
j0
j0f
j1
+10 -4
View File
@@ -9,7 +9,6 @@ license = "MIT OR Apache-2.0"
anyhow.workspace = true
# This is not directly used but is required so we can enable `gmp-mpfr-sys/force-cross`.
gmp-mpfr-sys = { workspace = true, optional = true }
gungraun = { workspace = true, optional = true }
indicatif.workspace = true
libm = { workspace = true, default-features = true, features = ["unstable-public-internals"] }
libm-macros.workspace = true
@@ -20,14 +19,18 @@ rand_chacha.workspace = true
rayon.workspace = true
rug = { workspace = true, optional = true }
# Really dev dependencies, but those can't be optional
criterion = { workspace = true, optional = true }
gungraun = { workspace = true, optional = true }
[target.'cfg(target_family = "wasm")'.dependencies]
getrandom = { workspace = true, features = ["wasm_js"] }
indicatif = { workspace = true, features = ["wasmbind"] }
[build-dependencies]
rand = { workspace = true, optional = true }
[dev-dependencies]
criterion.workspace = true
libtest-mimic.workspace = true
[features]
@@ -43,8 +46,10 @@ build-mpfr = ["dep:rug", "dep:gmp-mpfr-sys"]
# Build our own musl for testing and benchmarks
build-musl = ["dep:musl-math-sys"]
# Enable report generation without bringing in more dependencies by default
benchmarking-reports = ["criterion/plotters", "criterion/html_reports"]
# Config for wall time benchmarks. Plotters and html_reports bring in extra
# deps so are off by default for CI.
benchmarking-reports = ["walltime", "criterion/plotters", "criterion/html_reports"]
walltime = ["dep:criterion"]
# Enable icount benchmarks (requires gungraun-runner and valgrind locally)
icount = ["dep:gungraun"]
@@ -60,6 +65,7 @@ required-features = ["icount"]
[[bench]]
name = "random"
harness = false
required-features = ["walltime"]
[[test]]
# No harness so that we can skip tests at runtime based on env. Prefixed with
@@ -331,10 +331,14 @@ fn icount_bench_print_hf128(x: f128) -> String {
icount_bench_fmodf16_group,
icount_bench_fmodf_group,
icount_bench_frexp_group,
icount_bench_frexpf128_group,
icount_bench_frexpf16_group,
icount_bench_frexpf_group,
icount_bench_hypot_group,
icount_bench_hypotf_group,
icount_bench_ilogb_group,
icount_bench_ilogbf128_group,
icount_bench_ilogbf16_group,
icount_bench_ilogbf_group,
icount_bench_j0_group,
icount_bench_j0f_group,
@@ -25,12 +25,16 @@ impl Float for f8 {
const NEG_ZERO: Self = Self(0b1_0000_000);
const ONE: Self = Self(0b0_0111_000);
const NEG_ONE: Self = Self(0b1_0111_000);
const MAX: Self = Self(0b0_1110_111);
const MIN: Self = Self(0b1_1110_111);
const INFINITY: Self = Self(0b0_1111_000);
const NEG_INFINITY: Self = Self(0b1_1111_000);
const MAX: Self = Self(0b0_1110_111);
const MIN: Self = Self(0b1_1110_111);
const NAN: Self = Self(0b0_1111_100);
const SNAN: Self = Self(0b0_1111_001);
const NEG_NAN: Self = Self(0b1_1111_100);
const NEG_SNAN: Self = Self(0b1_1111_001);
const MIN_POSITIVE_NORMAL: Self = Self(1 << Self::SIG_BITS);
// FIXME: incorrect values
const EPSILON: Self = Self::ZERO;
@@ -44,6 +48,7 @@ impl Float for f8 {
const SIG_MASK: Self::Int = 0b0_0000_111;
const EXP_MASK: Self::Int = 0b0_1111_000;
const IMPLICIT_BIT: Self::Int = 0b0_0001_000;
const SIG_TOP_BIT: Self::Int = Self::IMPLICIT_BIT >> 1;
fn to_bits(self) -> Self::Int {
self.0
@@ -460,7 +460,8 @@ fn fmodf16_cases() -> Vec<TestCase<op::fmodf16::Routine>> {
vec![]
}
fn frexp_cases() -> Vec<TestCase<op::frexp::Routine>> {
#[cfg(f16_enabled)]
fn frexpf16_cases() -> Vec<TestCase<op::frexpf16::Routine>> {
vec![]
}
@@ -468,6 +469,15 @@ fn frexpf_cases() -> Vec<TestCase<op::frexpf::Routine>> {
vec![]
}
fn frexp_cases() -> Vec<TestCase<op::frexp::Routine>> {
vec![]
}
#[cfg(f128_enabled)]
fn frexpf128_cases() -> Vec<TestCase<op::frexpf128::Routine>> {
vec![]
}
fn hypot_cases() -> Vec<TestCase<op::hypot::Routine>> {
vec![]
}
@@ -476,7 +486,8 @@ fn hypotf_cases() -> Vec<TestCase<op::hypotf::Routine>> {
vec![]
}
fn ilogb_cases() -> Vec<TestCase<op::ilogb::Routine>> {
#[cfg(f16_enabled)]
fn ilogbf16_cases() -> Vec<TestCase<op::ilogbf16::Routine>> {
vec![]
}
@@ -484,6 +495,15 @@ fn ilogbf_cases() -> Vec<TestCase<op::ilogbf::Routine>> {
vec![]
}
fn ilogb_cases() -> Vec<TestCase<op::ilogb::Routine>> {
vec![]
}
#[cfg(f128_enabled)]
fn ilogbf128_cases() -> Vec<TestCase<op::ilogbf128::Routine>> {
vec![]
}
fn j0_cases() -> Vec<TestCase<op::j0::Routine>> {
vec![]
}
@@ -5,7 +5,7 @@
use libm::support::Float;
use rand::distr::{Alphanumeric, StandardUniform};
use rand::prelude::Distribution;
use rand::{Rng, SeedableRng};
use rand::{RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;
use super::KnownSize;
@@ -162,8 +162,12 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
fmodf16,
frexp,
frexpf,
frexpf128,
frexpf16,
ilogb,
ilogbf,
ilogbf128,
ilogbf16,
jn,
jnf,
ldexp,
@@ -324,43 +328,6 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
}
}
impl MpOp for crate::op::[<frexp $suffix>]::Routine {
type MpTy = MpFloat;
fn new_mp() -> Self::MpTy {
new_mpfloat::<Self::FTy>()
}
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
this.assign(input.0);
let exp = this.frexp_mut();
(prep_retval::<Self::FTy>(this, Ordering::Equal), exp)
}
}
impl MpOp for crate::op::[<ilogb $suffix>]::Routine {
type MpTy = MpFloat;
fn new_mp() -> Self::MpTy {
new_mpfloat::<Self::FTy>()
}
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
this.assign(input.0);
// `get_exp` follows `frexp` for `0.5 <= |m| < 1.0`. Adjust the exponent by
// one to scale the significand to `1.0 <= |m| < 2.0`.
this.get_exp().map(|v| v - 1).unwrap_or_else(|| {
if this.is_infinite() {
i32::MAX
} else {
// Zero or NaN
i32::MIN
}
})
}
}
impl MpOp for crate::op::[<jn $suffix>]::Routine {
type MpTy = MpFloat;
@@ -505,6 +472,43 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
}
}
impl MpOp for crate::op::[<frexp $suffix>]::Routine {
type MpTy = MpFloat;
fn new_mp() -> Self::MpTy {
new_mpfloat::<Self::FTy>()
}
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
this.assign(input.0);
let exp = this.frexp_mut();
(prep_retval::<Self::FTy>(this, Ordering::Equal), exp)
}
}
impl MpOp for crate::op::[<ilogb $suffix>]::Routine {
type MpTy = MpFloat;
fn new_mp() -> Self::MpTy {
new_mpfloat::<Self::FTy>()
}
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
this.assign(input.0);
// `get_exp` follows `frexp` for `0.5 <= |m| < 1.0`. Adjust the exponent by
// one to scale the significand to `1.0 <= |m| < 2.0`.
this.get_exp().map(|v| v - 1).unwrap_or_else(|| {
if this.is_infinite() {
i32::MAX
} else {
// Zero or NaN
i32::MIN
}
})
}
}
// `ldexp` and `scalbn` are the same for binary floating point, so just forward all
// methods.
impl MpOp for crate::op::[<ldexp $suffix>]::Routine {
@@ -1,9 +1,10 @@
//! Configuration for skipping or changing the result for individual test cases (inputs) rather
//! than ignoring entire tests.
use BaseName as Bn;
use CheckBasis::{Mpfr, Musl};
use Identifier as Id;
use libm::support::CastFrom;
use {BaseName as Bn, Identifier as Id};
use crate::{BaseName, CheckBasis, CheckCtx, Float, Identifier, Int, TestResult};
@@ -222,6 +223,15 @@ fn check_float<F: Float>(input: (f32,), actual: F, expected: F, ctx: &CheckCtx)
return XFAIL_NOCHECK;
}
// the testing infrastructure doesn't account for allowed ulp in the case of overflow
if matches!(ctx.base_name, BaseName::Lgamma | BaseName::LgammaR)
&& input.0 == 4.0850034e36
&& expected.is_infinite()
&& actual == F::MAX
{
return XFAIL_NOCHECK;
}
// FIXME(correctness): lgammaf has high relative inaccuracy near its zeroes
if matches!(ctx.base_name, BaseName::Lgamma | BaseName::LgammaR)
&& input.0 > -13.0625
@@ -260,7 +260,7 @@ fn validate_int<I, Input>(actual: I, expected: I, input: Input, ctx: &CheckCtx)
Ok(())
}
impl_int!(u32, i32, u64, i64);
impl_int!(u16, i16, u32, i32, u64, i64, u128, i128);
/* trait implementations for floats */
@@ -456,3 +456,15 @@ fn validate<'a>(
(f32, f32);
(f64, f64);
);
#[cfg(f16_enabled)]
impl_tuples!(
(f16, i32);
(f16, f16);
);
#[cfg(f128_enabled)]
impl_tuples!(
(f128, i32);
(f128, f128);
);
@@ -10,7 +10,7 @@
use libm_test::bigint_fuzz_iteration_count;
use libm_test::generate::random::SEED;
use rand::{Rng, SeedableRng};
use rand::{RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;
use rug::Assign;
use rug::integer::Order;
+1 -1
View File
@@ -17,7 +17,7 @@ rust-version = "1.67"
[dev-dependencies]
# FIXME(msrv): switch to `no-panic.workspace` when possible
no-panic = "0.1.35"
no-panic = "0.1.36"
[features]
default = ["arch"]
@@ -202,6 +202,8 @@ pub fn $func($($arg: $arg_typ),*) -> ($($ret_typ),*) {
(fn fminimum(x: f16, y: f16) -> (f16); => fminimumf16);
(fn fminimum_num(x: f16, y: f16) -> (f16); => fminimum_numf16);
(fn fmod(x: f16, y: f16) -> (f16); => fmodf16);
(fn frexp(x: f16) -> (f16, i32); => frexpf16);
(fn ilogb(x: f16) -> (i32); => ilogbf16);
(fn ldexp(x: f16, n: i32) -> (f16); => ldexpf16);
(fn rint(x: f16) -> (f16); => rintf16);
(fn round(x: f16) -> (f16); => roundf16);
@@ -231,6 +233,8 @@ pub fn $func($($arg: $arg_typ),*) -> ($($ret_typ),*) {
(fn fminimum(x: f128, y: f128) -> (f128); => fminimumf128);
(fn fminimum_num(x: f128, y: f128) -> (f128); => fminimum_numf128);
(fn fmod(x: f128, y: f128) -> (f128); => fmodf128);
(fn frexp(x: f128) -> (f128, i32); => frexpf128);
(fn ilogb(x: f128) -> (i32); => ilogbf128);
(fn ldexp(x: f128, n: i32) -> (f128); => ldexpf128);
(fn rint(x: f128) -> (f128); => rintf128);
(fn round(x: f128) -> (f128); => roundf128);
@@ -380,7 +380,7 @@ pub fn exp2(mut x: f64) -> f64 {
let mut i0 = ui as u32;
i0 = i0.wrapping_add(TBLSIZE as u32 / 2);
let ku = i0 / TBLSIZE as u32 * TBLSIZE as u32;
let ki = div!(ku as i32, TBLSIZE as i32);
let ki = (ku as i32) / TBLSIZE as i32;
i0 %= TBLSIZE as u32;
let uf = f64::from_bits(ui) - redux;
let mut z = x - uf;
@@ -77,9 +77,12 @@ pub fn fmaxf128(x: f128, y: f128) -> f128 {
#[cfg(test)]
mod tests {
use super::*;
use crate::support::hex_float::Hexi;
use crate::support::{Float, Hexf};
fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
// Note that (YaN, sNaN) and (sNaN, YaN) results differ from 754-2008. This is intentional,
// see comments in the generic implementations.
let cases = [
(F::ZERO, F::ZERO, F::ZERO),
(F::ZERO, F::ONE, F::ZERO),
@@ -88,6 +91,8 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ZERO, F::NEG_INFINITY, F::NEG_INFINITY),
(F::ZERO, F::NAN, F::ZERO),
(F::ZERO, F::NEG_NAN, F::ZERO),
(F::ZERO, F::SNAN, F::ZERO),
(F::ZERO, F::NEG_SNAN, F::ZERO),
(F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_ZERO, F::ONE, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_ONE, F::NEG_ONE),
@@ -95,6 +100,8 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ZERO, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_ZERO, F::NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::SNAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO),
(F::ONE, F::ZERO, F::ZERO),
(F::ONE, F::NEG_ZERO, F::NEG_ZERO),
(F::ONE, F::ONE, F::ONE),
@@ -103,6 +110,8 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ONE, F::NEG_INFINITY, F::NEG_INFINITY),
(F::ONE, F::NAN, F::ONE),
(F::ONE, F::NEG_NAN, F::ONE),
(F::ONE, F::SNAN, F::ONE),
(F::ONE, F::NEG_SNAN, F::ONE),
(F::NEG_ONE, F::ZERO, F::NEG_ONE),
(F::NEG_ONE, F::NEG_ZERO, F::NEG_ONE),
(F::NEG_ONE, F::ONE, F::NEG_ONE),
@@ -111,6 +120,8 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ONE, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_ONE, F::NAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_NAN, F::NEG_ONE),
(F::NEG_ONE, F::SNAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE),
(F::INFINITY, F::ZERO, F::ZERO),
(F::INFINITY, F::NEG_ZERO, F::NEG_ZERO),
(F::INFINITY, F::ONE, F::ONE),
@@ -119,6 +130,8 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::INFINITY, F::NEG_INFINITY, F::NEG_INFINITY),
(F::INFINITY, F::NAN, F::INFINITY),
(F::INFINITY, F::NEG_NAN, F::INFINITY),
(F::INFINITY, F::SNAN, F::INFINITY),
(F::INFINITY, F::NEG_SNAN, F::INFINITY),
(F::NEG_INFINITY, F::ZERO, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_ZERO, F::NEG_INFINITY),
(F::NEG_INFINITY, F::ONE, F::NEG_INFINITY),
@@ -127,6 +140,8 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY),
(F::NAN, F::ZERO, F::ZERO),
(F::NAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NAN, F::ONE, F::ONE),
@@ -140,6 +155,18 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_NAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_NAN, F::INFINITY, F::INFINITY),
(F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::SNAN, F::ZERO, F::ZERO),
(F::SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::SNAN, F::ONE, F::ONE),
(F::SNAN, F::NEG_ONE, F::NEG_ONE),
(F::SNAN, F::INFINITY, F::INFINITY),
(F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_SNAN, F::ZERO, F::ZERO),
(F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_SNAN, F::ONE, F::ONE),
(F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_SNAN, F::INFINITY, F::INFINITY),
(F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
];
for (x, y, res) in cases {
@@ -147,12 +174,29 @@ fn fmin_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
assert_biteq!(val, res, "fmin({}, {})", Hexf(x), Hexf(y));
}
// Ordering between zeros and NaNs does not matter
// Ordering between zeros does not matter
assert_eq!(f(F::ZERO, F::NEG_ZERO), F::ZERO);
assert_eq!(f(F::NEG_ZERO, F::ZERO), F::ZERO);
assert!(f(F::NAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::NAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan());
// Selection between NaNs does not matter, it just must be quiet
assert!(f(F::NAN, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan());
// These operations should technically return a qnan, but LLVM optimizes out our
// `* 1.0` canonicalization.
assert!(f(F::NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NAN, F::SNAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_NAN, F::SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::SNAN).is_nan());
assert!(f(F::SNAN, F::NAN).is_nan());
assert!(f(F::SNAN, F::NAN).is_nan());
assert!(f(F::SNAN, F::NEG_NAN).is_nan());
assert!(f(F::SNAN, F::NEG_SNAN).is_nan());
}
#[test]
@@ -186,6 +230,8 @@ fn fmax_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ZERO, F::NEG_INFINITY, F::ZERO),
(F::ZERO, F::NAN, F::ZERO),
(F::ZERO, F::NEG_NAN, F::ZERO),
(F::ZERO, F::SNAN, F::ZERO),
(F::ZERO, F::NEG_SNAN, F::ZERO),
(F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_ZERO, F::ONE, F::ONE),
(F::NEG_ZERO, F::NEG_ONE, F::NEG_ZERO),
@@ -193,6 +239,8 @@ fn fmax_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ZERO, F::NEG_INFINITY, F::NEG_ZERO),
(F::NEG_ZERO, F::NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::SNAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO),
(F::ONE, F::ZERO, F::ONE),
(F::ONE, F::NEG_ZERO, F::ONE),
(F::ONE, F::ONE, F::ONE),
@@ -201,6 +249,8 @@ fn fmax_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ONE, F::NEG_INFINITY, F::ONE),
(F::ONE, F::NAN, F::ONE),
(F::ONE, F::NEG_NAN, F::ONE),
(F::ONE, F::SNAN, F::ONE),
(F::ONE, F::NEG_SNAN, F::ONE),
(F::NEG_ONE, F::ZERO, F::ZERO),
(F::NEG_ONE, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_ONE, F::ONE, F::ONE),
@@ -209,6 +259,8 @@ fn fmax_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ONE, F::NEG_INFINITY, F::NEG_ONE),
(F::NEG_ONE, F::NAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_NAN, F::NEG_ONE),
(F::NEG_ONE, F::SNAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE),
(F::INFINITY, F::ZERO, F::INFINITY),
(F::INFINITY, F::NEG_ZERO, F::INFINITY),
(F::INFINITY, F::ONE, F::INFINITY),
@@ -217,6 +269,8 @@ fn fmax_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::INFINITY, F::NEG_INFINITY, F::INFINITY),
(F::INFINITY, F::NAN, F::INFINITY),
(F::INFINITY, F::NEG_NAN, F::INFINITY),
(F::INFINITY, F::SNAN, F::INFINITY),
(F::INFINITY, F::NEG_SNAN, F::INFINITY),
(F::NEG_INFINITY, F::ZERO, F::ZERO),
(F::NEG_INFINITY, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_INFINITY, F::ONE, F::ONE),
@@ -225,6 +279,8 @@ fn fmax_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY),
(F::NAN, F::ZERO, F::ZERO),
(F::NAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NAN, F::ONE, F::ONE),
@@ -238,19 +294,54 @@ fn fmax_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_NAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_NAN, F::INFINITY, F::INFINITY),
(F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::SNAN, F::ZERO, F::ZERO),
(F::SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::SNAN, F::ONE, F::ONE),
(F::SNAN, F::NEG_ONE, F::NEG_ONE),
(F::SNAN, F::INFINITY, F::INFINITY),
(F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_SNAN, F::ZERO, F::ZERO),
(F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_SNAN, F::ONE, F::ONE),
(F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_SNAN, F::INFINITY, F::INFINITY),
(F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
];
for (x, y, res) in cases {
let val = f(x, y);
assert_biteq!(val, res, "fmax({}, {})", Hexf(x), Hexf(y));
assert_biteq!(
val,
res,
"fmax({}, {}) ({}, {})",
Hexf(x),
Hexf(y),
Hexi(x.to_bits()),
Hexi(y.to_bits()),
);
}
// Ordering between zeros and NaNs does not matter
// Ordering between zeros
assert_eq!(f(F::ZERO, F::NEG_ZERO), F::ZERO);
assert_eq!(f(F::NEG_ZERO, F::ZERO), F::ZERO);
assert!(f(F::NAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::NAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan());
// Selection between NaNs does not matter, it just must be quiet
assert!(f(F::NAN, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan());
assert!(f(F::NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NAN, F::SNAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_NAN, F::SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::SNAN).is_nan());
assert!(f(F::SNAN, F::NAN).is_nan());
assert!(f(F::SNAN, F::NEG_NAN).is_nan());
assert!(f(F::SNAN, F::NEG_SNAN).is_nan());
assert!(f(F::SNAN, F::SNAN).is_nan());
}
#[test]
@@ -69,6 +69,7 @@ pub fn fmaximumf128(x: f128, y: f128) -> f128 {
#[cfg(test)]
mod tests {
use super::*;
use crate::support::hex_float::Hexi;
use crate::support::{Float, Hexf};
fn fminimum_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
@@ -122,29 +123,63 @@ fn fminimum_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NAN, F::INFINITY, F::NAN),
(F::NAN, F::NEG_INFINITY, F::NAN),
(F::NAN, F::NAN, F::NAN),
(F::NAN, F::SNAN, F::NAN),
];
for (x, y, res) in cases {
let val = f(x, y);
assert_biteq!(val, res, "fminimum({}, {})", Hexf(x), Hexf(y));
assert_biteq!(
val,
res,
"fminimum({}, {}) ({}, {})",
Hexf(x),
Hexf(y),
Hexi(x.to_bits()),
Hexi(y.to_bits()),
);
}
// Ordering between NaNs does not matter
assert!(f(F::NAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::NAN).is_nan());
assert!(f(F::ZERO, F::NEG_NAN).is_nan());
assert!(f(F::NEG_ZERO, F::NEG_NAN).is_nan());
assert!(f(F::ONE, F::NEG_NAN).is_nan());
assert!(f(F::NEG_ONE, F::NEG_NAN).is_nan());
assert!(f(F::INFINITY, F::NEG_NAN).is_nan());
assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::ZERO).is_nan());
assert!(f(F::NEG_NAN, F::NEG_ZERO).is_nan());
assert!(f(F::NEG_NAN, F::ONE).is_nan());
assert!(f(F::NEG_NAN, F::NEG_ONE).is_nan());
assert!(f(F::NEG_NAN, F::INFINITY).is_nan());
assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_nan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan());
// On platforms where operations only return a single canonical NaN (e.g. RISC-V), the
// result may not exactly match one of the inputs which is fine.
assert!(f(F::NAN, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NAN).is_qnan());
assert!(f(F::ZERO, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_ZERO, F::NEG_NAN).is_qnan());
assert!(f(F::ONE, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_ONE, F::NEG_NAN).is_qnan());
assert!(f(F::INFINITY, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::ZERO).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_ZERO).is_qnan());
assert!(f(F::NEG_NAN, F::ONE).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_ONE).is_qnan());
assert!(f(F::NEG_NAN, F::INFINITY).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan());
// These operations should technically return a qnan, but LLVM optimizes out our
// `* 1.0` canonicalization.
assert!(f(F::INFINITY, F::SNAN,).is_nan());
assert!(f(F::NEG_INFINITY, F::SNAN,).is_nan());
assert!(f(F::NEG_ONE, F::SNAN,).is_nan());
assert!(f(F::NEG_SNAN, F::INFINITY).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_INFINITY).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_ONE).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_ZERO).is_nan());
assert!(f(F::NEG_SNAN, F::ONE).is_nan());
assert!(f(F::NEG_SNAN, F::ZERO).is_nan());
assert!(f(F::NEG_ZERO, F::SNAN,).is_nan());
assert!(f(F::ONE, F::SNAN,).is_nan());
assert!(f(F::SNAN, F::INFINITY,).is_nan());
assert!(f(F::SNAN, F::NEG_INFINITY,).is_nan());
assert!(f(F::SNAN, F::NEG_ONE,).is_nan());
assert!(f(F::SNAN, F::NEG_SNAN,).is_nan());
assert!(f(F::SNAN, F::NEG_ZERO,).is_nan());
assert!(f(F::SNAN, F::ONE,).is_nan());
assert!(f(F::SNAN, F::SNAN,).is_nan());
assert!(f(F::SNAN, F::ZERO,).is_nan());
assert!(f(F::ZERO, F::SNAN,).is_nan());
}
#[test]
@@ -220,29 +255,63 @@ fn fmaximum_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NAN, F::INFINITY, F::NAN),
(F::NAN, F::NEG_INFINITY, F::NAN),
(F::NAN, F::NAN, F::NAN),
(F::NAN, F::SNAN, F::NAN),
];
for (x, y, res) in cases {
let val = f(x, y);
assert_biteq!(val, res, "fmaximum({}, {})", Hexf(x), Hexf(y));
assert_biteq!(
val,
res,
"fmaximum({}, {}) ({}, {})",
Hexf(x),
Hexf(y),
Hexi(x.to_bits()),
Hexi(y.to_bits()),
);
}
// Ordering between NaNs does not matter
assert!(f(F::NAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::NAN).is_nan());
assert!(f(F::ZERO, F::NEG_NAN).is_nan());
assert!(f(F::NEG_ZERO, F::NEG_NAN).is_nan());
assert!(f(F::ONE, F::NEG_NAN).is_nan());
assert!(f(F::NEG_ONE, F::NEG_NAN).is_nan());
assert!(f(F::INFINITY, F::NEG_NAN).is_nan());
assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::ZERO).is_nan());
assert!(f(F::NEG_NAN, F::NEG_ZERO).is_nan());
assert!(f(F::NEG_NAN, F::ONE).is_nan());
assert!(f(F::NEG_NAN, F::NEG_ONE).is_nan());
assert!(f(F::NEG_NAN, F::INFINITY).is_nan());
assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_nan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan());
// On platforms where operations only return a single canonical NaN (e.g. RISC-V), the
// result may not exactly match one of the inputs which is fine.
assert!(f(F::NAN, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NAN).is_qnan());
assert!(f(F::ZERO, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_ZERO, F::NEG_NAN).is_qnan());
assert!(f(F::ONE, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_ONE, F::NEG_NAN).is_qnan());
assert!(f(F::INFINITY, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::ZERO).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_ZERO).is_qnan());
assert!(f(F::NEG_NAN, F::ONE).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_ONE).is_qnan());
assert!(f(F::NEG_NAN, F::INFINITY).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan());
// These operations should technically return a qnan, but LLVM optimizes out our
// `* 1.0` canonicalization.
assert!(f(F::INFINITY, F::SNAN,).is_nan());
assert!(f(F::NEG_INFINITY, F::SNAN,).is_nan());
assert!(f(F::NEG_ONE, F::SNAN,).is_nan());
assert!(f(F::NEG_SNAN, F::INFINITY).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_INFINITY).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_ONE).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_ZERO).is_nan());
assert!(f(F::NEG_SNAN, F::ONE).is_nan());
assert!(f(F::NEG_SNAN, F::ZERO).is_nan());
assert!(f(F::NEG_ZERO, F::SNAN,).is_nan());
assert!(f(F::ONE, F::SNAN,).is_nan());
assert!(f(F::SNAN, F::INFINITY,).is_nan());
assert!(f(F::SNAN, F::NEG_INFINITY,).is_nan());
assert!(f(F::SNAN, F::NEG_ONE,).is_nan());
assert!(f(F::SNAN, F::NEG_SNAN,).is_nan());
assert!(f(F::SNAN, F::NEG_ZERO,).is_nan());
assert!(f(F::SNAN, F::ONE,).is_nan());
assert!(f(F::SNAN, F::SNAN,).is_nan());
assert!(f(F::SNAN, F::ZERO,).is_nan());
assert!(f(F::ZERO, F::SNAN,).is_nan());
}
#[test]
@@ -69,6 +69,7 @@ pub fn fmaximum_numf128(x: f128, y: f128) -> f128 {
#[cfg(test)]
mod tests {
use super::*;
use crate::support::hex_float::Hexi;
use crate::support::{Float, Hexf};
fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
@@ -81,6 +82,8 @@ fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ZERO, F::NEG_INFINITY, F::NEG_INFINITY),
(F::ZERO, F::NAN, F::ZERO),
(F::ZERO, F::NEG_NAN, F::ZERO),
(F::ZERO, F::SNAN, F::ZERO),
(F::ZERO, F::NEG_SNAN, F::ZERO),
(F::NEG_ZERO, F::ZERO, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_ZERO, F::ONE, F::NEG_ZERO),
@@ -89,6 +92,8 @@ fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ZERO, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_ZERO, F::NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::SNAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO),
(F::ONE, F::ZERO, F::ZERO),
(F::ONE, F::NEG_ZERO, F::NEG_ZERO),
(F::ONE, F::ONE, F::ONE),
@@ -97,6 +102,8 @@ fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ONE, F::NEG_INFINITY, F::NEG_INFINITY),
(F::ONE, F::NAN, F::ONE),
(F::ONE, F::NEG_NAN, F::ONE),
(F::ONE, F::SNAN, F::ONE),
(F::ONE, F::NEG_SNAN, F::ONE),
(F::NEG_ONE, F::ZERO, F::NEG_ONE),
(F::NEG_ONE, F::NEG_ZERO, F::NEG_ONE),
(F::NEG_ONE, F::ONE, F::NEG_ONE),
@@ -105,6 +112,8 @@ fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ONE, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_ONE, F::NAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_NAN, F::NEG_ONE),
(F::NEG_ONE, F::SNAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE),
(F::INFINITY, F::ZERO, F::ZERO),
(F::INFINITY, F::NEG_ZERO, F::NEG_ZERO),
(F::INFINITY, F::ONE, F::ONE),
@@ -113,6 +122,8 @@ fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::INFINITY, F::NEG_INFINITY, F::NEG_INFINITY),
(F::INFINITY, F::NAN, F::INFINITY),
(F::INFINITY, F::NEG_NAN, F::INFINITY),
(F::INFINITY, F::SNAN, F::INFINITY),
(F::INFINITY, F::NEG_SNAN, F::INFINITY),
(F::NEG_INFINITY, F::ZERO, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_ZERO, F::NEG_INFINITY),
(F::NEG_INFINITY, F::ONE, F::NEG_INFINITY),
@@ -121,6 +132,8 @@ fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY),
(F::NAN, F::ZERO, F::ZERO),
(F::NAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NAN, F::ONE, F::ONE),
@@ -134,17 +147,52 @@ fn fminimum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_NAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_NAN, F::INFINITY, F::INFINITY),
(F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::SNAN, F::ZERO, F::ZERO),
(F::SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::SNAN, F::ONE, F::ONE),
(F::SNAN, F::NEG_ONE, F::NEG_ONE),
(F::SNAN, F::INFINITY, F::INFINITY),
(F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_SNAN, F::ZERO, F::ZERO),
(F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_SNAN, F::ONE, F::ONE),
(F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_SNAN, F::INFINITY, F::INFINITY),
(F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
];
for (x, y, expected) in cases {
let actual = f(x, y);
assert_biteq!(actual, expected, "fminimum_num({}, {})", Hexf(x), Hexf(y));
assert_biteq!(
actual,
expected,
"fminimum_num({}, {}) ({}, {})",
Hexf(x),
Hexf(y),
Hexi(x.to_bits()),
Hexi(y.to_bits()),
);
}
// Ordering between NaNs does not matter
assert!(f(F::NAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::NAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan());
// Selection between NaNs does not matter, it just must be quiet
assert!(f(F::NAN, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan());
// These operations should technically return a qnan, but LLVM optimizes out our
// `* 1.0` canonicalization.
assert!(f(F::NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NAN, F::SNAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_NAN, F::SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::SNAN).is_nan());
assert!(f(F::SNAN, F::NAN).is_nan());
assert!(f(F::SNAN, F::NEG_NAN).is_nan());
assert!(f(F::SNAN, F::NEG_SNAN).is_nan());
assert!(f(F::SNAN, F::SNAN).is_nan());
}
#[test]
@@ -179,6 +227,8 @@ fn fmaximum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ZERO, F::NEG_INFINITY, F::ZERO),
(F::ZERO, F::NAN, F::ZERO),
(F::ZERO, F::NEG_NAN, F::ZERO),
(F::ZERO, F::SNAN, F::ZERO),
(F::ZERO, F::NEG_SNAN, F::ZERO),
(F::NEG_ZERO, F::ZERO, F::ZERO),
(F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_ZERO, F::ONE, F::ONE),
@@ -187,6 +237,8 @@ fn fmaximum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ZERO, F::NEG_INFINITY, F::NEG_ZERO),
(F::NEG_ZERO, F::NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO),
(F::NEG_ZERO, F::SNAN, F::NEG_ZERO),
(F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO),
(F::ONE, F::ZERO, F::ONE),
(F::ONE, F::NEG_ZERO, F::ONE),
(F::ONE, F::ONE, F::ONE),
@@ -195,6 +247,8 @@ fn fmaximum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::ONE, F::NEG_INFINITY, F::ONE),
(F::ONE, F::NAN, F::ONE),
(F::ONE, F::NEG_NAN, F::ONE),
(F::ONE, F::SNAN, F::ONE),
(F::ONE, F::NEG_SNAN, F::ONE),
(F::NEG_ONE, F::ZERO, F::ZERO),
(F::NEG_ONE, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_ONE, F::ONE, F::ONE),
@@ -203,6 +257,8 @@ fn fmaximum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_ONE, F::NEG_INFINITY, F::NEG_ONE),
(F::NEG_ONE, F::NAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_NAN, F::NEG_ONE),
(F::NEG_ONE, F::SNAN, F::NEG_ONE),
(F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE),
(F::INFINITY, F::ZERO, F::INFINITY),
(F::INFINITY, F::NEG_ZERO, F::INFINITY),
(F::INFINITY, F::ONE, F::INFINITY),
@@ -211,6 +267,8 @@ fn fmaximum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::INFINITY, F::NEG_INFINITY, F::INFINITY),
(F::INFINITY, F::NAN, F::INFINITY),
(F::INFINITY, F::NEG_NAN, F::INFINITY),
(F::INFINITY, F::SNAN, F::INFINITY),
(F::INFINITY, F::NEG_SNAN, F::INFINITY),
(F::NEG_INFINITY, F::ZERO, F::ZERO),
(F::NEG_INFINITY, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_INFINITY, F::ONE, F::ONE),
@@ -219,6 +277,8 @@ fn fmaximum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY),
(F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY),
(F::NAN, F::ZERO, F::ZERO),
(F::NAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NAN, F::ONE, F::ONE),
@@ -232,17 +292,52 @@ fn fmaximum_num_spec_test<F: Float>(f: impl Fn(F, F) -> F) {
(F::NEG_NAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_NAN, F::INFINITY, F::INFINITY),
(F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::SNAN, F::ZERO, F::ZERO),
(F::SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::SNAN, F::ONE, F::ONE),
(F::SNAN, F::NEG_ONE, F::NEG_ONE),
(F::SNAN, F::INFINITY, F::INFINITY),
(F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
(F::NEG_SNAN, F::ZERO, F::ZERO),
(F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO),
(F::NEG_SNAN, F::ONE, F::ONE),
(F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE),
(F::NEG_SNAN, F::INFINITY, F::INFINITY),
(F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY),
];
for (x, y, expected) in cases {
let actual = f(x, y);
assert_biteq!(actual, expected, "fmaximum_num({}, {})", Hexf(x), Hexf(y));
assert_biteq!(
actual,
expected,
"fmaximum_num({}, {}) ({}, {})",
Hexf(x),
Hexf(y),
Hexi(x.to_bits()),
Hexi(y.to_bits()),
);
}
// Ordering between NaNs does not matter
assert!(f(F::NAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_NAN, F::NAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan());
// Selection between NaNs does not matter, it just must be quiet
assert!(f(F::NAN, F::NEG_NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NAN).is_qnan());
assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan());
// These operations should technically return a qnan, but LLVM optimizes out our
// `* 1.0` canonicalization.
assert!(f(F::NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NAN, F::SNAN).is_nan());
assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_NAN, F::SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan());
assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan());
assert!(f(F::NEG_SNAN, F::SNAN).is_nan());
assert!(f(F::SNAN, F::NAN).is_nan());
assert!(f(F::SNAN, F::NEG_NAN).is_nan());
assert!(f(F::SNAN, F::NEG_SNAN).is_nan());
assert!(f(F::SNAN, F::SNAN).is_nan());
}
#[test]
@@ -1,21 +1,34 @@
/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2.
///
/// That is, `x * 2^p` will represent the input value.
// Placeholder so we can have `frexpf16` in the `Float` trait.
#[cfg(f16_enabled)]
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn frexpf16(x: f16) -> (f16, i32) {
super::generic::frexp(x)
}
/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2.
///
/// That is, `x * 2^p` will represent the input value.
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn frexpf(x: f32) -> (f32, i32) {
super::generic::frexp(x)
}
/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2.
///
/// That is, `x * 2^p` will represent the input value.
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn frexp(x: f64) -> (f64, i32) {
let mut y = x.to_bits();
let ee = ((y >> 52) & 0x7ff) as i32;
if ee == 0 {
if x != 0.0 {
let x1p64 = f64::from_bits(0x43f0000000000000);
let (x, e) = frexp(x * x1p64);
return (x, e - 64);
}
return (x, 0);
} else if ee == 0x7ff {
return (x, 0);
}
let e = ee - 0x3fe;
y &= 0x800fffffffffffff;
y |= 0x3fe0000000000000;
return (f64::from_bits(y), e);
super::generic::frexp(x)
}
/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2.
///
/// That is, `x * 2^p` will represent the input value.
#[cfg(f128_enabled)]
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn frexpf128(x: f128) -> (f128, i32) {
super::generic::frexp(x)
}
@@ -1,22 +0,0 @@
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn frexpf(x: f32) -> (f32, i32) {
let mut y = x.to_bits();
let ee: i32 = ((y >> 23) & 0xff) as i32;
if ee == 0 {
if x != 0.0 {
let x1p64 = f32::from_bits(0x5f800000);
let (x, e) = frexpf(x * x1p64);
return (x, e - 64);
} else {
return (x, 0);
}
} else if ee == 0xff {
return (x, 0);
}
let e = ee - 0x7e;
y &= 0x807fffff;
y |= 0x3f000000;
(f32::from_bits(y), e)
}
@@ -46,7 +46,7 @@ pub fn ceil_status<F: Float>(x: F) -> FpResult<F> {
F::from_bits(ix)
} else {
// |x| < 1.0, raise an inexact exception since truncation will happen (unless x == 0).
if ix & F::SIG_MASK == F::Int::ZERO {
if ix & !F::SIGN_MASK == F::Int::ZERO {
status = Status::OK;
} else {
status = Status::INEXACT;
@@ -72,103 +72,83 @@ mod tests {
use super::*;
use crate::support::Hexf;
/// Test against https://en.cppreference.com/w/cpp/numeric/math/ceil
fn spec_test<F: Float>(cases: &[(F, F, Status)]) {
let roundtrip = [
F::ZERO,
F::ONE,
F::NEG_ONE,
F::NEG_ZERO,
F::INFINITY,
F::NEG_INFINITY,
];
for x in roundtrip {
let FpResult { val, status } = ceil_status(x);
assert_biteq!(val, x, "{}", Hexf(x));
assert_eq!(status, Status::OK, "{}", Hexf(x));
}
for &(x, res, res_stat) in cases {
let FpResult { val, status } = ceil_status(x);
assert_biteq!(val, res, "{}", Hexf(x));
assert_eq!(status, res_stat, "{}", Hexf(x));
}
macro_rules! cases {
($f:ty) => {
[
// roundtrip
(0.0, 0.0, Status::OK),
(-0.0, -0.0, Status::OK),
(1.0, 1.0, Status::OK),
(-1.0, -1.0, Status::OK),
(<$f>::INFINITY, <$f>::INFINITY, Status::OK),
(<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK),
// with rounding
(0.1, 1.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.5, 1.0, Status::INEXACT),
(-0.5, -0.0, Status::INEXACT),
(0.9, 1.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 2.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.5, 2.0, Status::INEXACT),
(-1.5, -1.0, Status::INEXACT),
(1.9, 2.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
]
};
}
/* Skipping f16 / f128 "sanity_check"s due to rejected literal lexing at MSRV */
#[track_caller]
fn check<F: Float>(cases: &[(F, F, Status)]) {
for &(x, exp_res, exp_stat) in cases {
let FpResult { val, status } = ceil_status(x);
assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x));
assert_eq!(
status,
exp_stat,
"{x:?} {} -> {exp_res:?} {}",
Hexf(x),
Hexf(exp_res)
);
}
}
#[test]
#[cfg(f16_enabled)]
fn spec_tests_f16() {
let cases = [
(0.1, 1.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.9, 1.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 2.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.9, 2.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
];
spec_test::<f16>(&cases);
fn check_f16() {
check::<f16>(&cases!(f16));
check::<f16>(&[
(hf16!("0x1p10"), hf16!("0x1p10"), Status::OK),
(hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK),
]);
}
#[test]
fn sanity_check_f32() {
assert_eq!(ceil(1.1f32), 2.0);
assert_eq!(ceil(2.9f32), 3.0);
fn check_f32() {
check::<f32>(&cases!(f32));
check::<f32>(&[
(hf32!("0x1p23"), hf32!("0x1p23"), Status::OK),
(hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK),
]);
}
#[test]
fn spec_tests_f32() {
let cases = [
(0.1, 1.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.9, 1.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 2.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.9, 2.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
];
spec_test::<f32>(&cases);
}
#[test]
fn sanity_check_f64() {
assert_eq!(ceil(1.1f64), 2.0);
assert_eq!(ceil(2.9f64), 3.0);
}
#[test]
fn spec_tests_f64() {
let cases = [
(0.1, 1.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.9, 1.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 2.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.9, 2.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
];
spec_test::<f64>(&cases);
fn check_f64() {
check::<f64>(&cases!(f64));
check::<f64>(&[
(hf64!("0x1p52"), hf64!("0x1p52"), Status::OK),
(hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK),
]);
}
#[test]
#[cfg(f128_enabled)]
fn spec_tests_f128() {
let cases = [
(0.1, 1.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.9, 1.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 2.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.9, 2.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
];
spec_test::<f128>(&cases);
check::<f128>(&cases!(f128));
check::<f128>(&[
(hf128!("0x1p112"), hf128!("0x1p112"), Status::OK),
(hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK),
]);
}
}
@@ -26,7 +26,6 @@ pub fn floor_status<F: Float>(x: F) -> FpResult<F> {
return FpResult::ok(x);
}
let status;
let res = if e >= 0 {
// |x| >= 1.0
let m = F::SIG_MASK >> e.unsigned();
@@ -35,9 +34,6 @@ pub fn floor_status<F: Float>(x: F) -> FpResult<F> {
return FpResult::ok(x);
}
// Otherwise, raise an inexact exception.
status = Status::INEXACT;
if x.is_sign_negative() {
ix += m;
}
@@ -45,26 +41,22 @@ pub fn floor_status<F: Float>(x: F) -> FpResult<F> {
ix &= !m;
F::from_bits(ix)
} else {
// |x| < 1.0, raise an inexact exception since truncation will happen.
if ix & F::SIG_MASK == F::Int::ZERO {
status = Status::OK;
} else {
status = Status::INEXACT;
// |x| < 1.0, zero or inexact with truncation
if (ix & !F::SIGN_MASK) == F::Int::ZERO {
return FpResult::ok(x);
}
if x.is_sign_positive() {
// 0.0 <= x < 1.0; rounding down goes toward +0.0.
F::ZERO
} else if ix << 1 != zero {
} else {
// -1.0 < x < 0.0; rounding down goes toward -1.0.
F::NEG_ONE
} else {
// -0.0 remains unchanged
x
}
};
FpResult::new(res, status)
FpResult::new(res, Status::INEXACT)
}
#[cfg(test)]
@@ -72,86 +64,83 @@ mod tests {
use super::*;
use crate::support::Hexf;
/// Test against https://en.cppreference.com/w/cpp/numeric/math/floor
fn spec_test<F: Float>(cases: &[(F, F, Status)]) {
let roundtrip = [
F::ZERO,
F::ONE,
F::NEG_ONE,
F::NEG_ZERO,
F::INFINITY,
F::NEG_INFINITY,
];
for x in roundtrip {
let FpResult { val, status } = floor_status(x);
assert_biteq!(val, x, "{}", Hexf(x));
assert_eq!(status, Status::OK, "{}", Hexf(x));
}
for &(x, res, res_stat) in cases {
let FpResult { val, status } = floor_status(x);
assert_biteq!(val, res, "{}", Hexf(x));
assert_eq!(status, res_stat, "{}", Hexf(x));
}
macro_rules! cases {
($f:ty) => {
[
// roundtrip
(0.0, 0.0, Status::OK),
(-0.0, -0.0, Status::OK),
(1.0, 1.0, Status::OK),
(-1.0, -1.0, Status::OK),
(<$f>::INFINITY, <$f>::INFINITY, Status::OK),
(<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK),
// with rounding
(0.1, 0.0, Status::INEXACT),
(-0.1, -1.0, Status::INEXACT),
(0.5, 0.0, Status::INEXACT),
(-0.5, -1.0, Status::INEXACT),
(0.9, 0.0, Status::INEXACT),
(-0.9, -1.0, Status::INEXACT),
(1.1, 1.0, Status::INEXACT),
(-1.1, -2.0, Status::INEXACT),
(1.5, 1.0, Status::INEXACT),
(-1.5, -2.0, Status::INEXACT),
(1.9, 1.0, Status::INEXACT),
(-1.9, -2.0, Status::INEXACT),
]
};
}
/* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */
#[track_caller]
fn check<F: Float>(cases: &[(F, F, Status)]) {
for &(x, exp_res, exp_stat) in cases {
let FpResult { val, status } = floor_status(x);
assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x));
assert_eq!(
status,
exp_stat,
"{x:?} {} -> {exp_res:?} {}",
Hexf(x),
Hexf(exp_res)
);
}
}
#[test]
#[cfg(f16_enabled)]
fn spec_tests_f16() {
let cases = [];
spec_test::<f16>(&cases);
fn check_f16() {
check::<f16>(&cases!(f16));
check::<f16>(&[
(hf16!("0x1p10"), hf16!("0x1p10"), Status::OK),
(hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK),
]);
}
#[test]
fn sanity_check_f32() {
assert_eq!(floor(0.5f32), 0.0);
assert_eq!(floor(1.1f32), 1.0);
assert_eq!(floor(2.9f32), 2.0);
fn check_f32() {
check::<f32>(&cases!(f32));
check::<f32>(&[
(hf32!("0x1p23"), hf32!("0x1p23"), Status::OK),
(hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK),
]);
}
#[test]
fn spec_tests_f32() {
let cases = [
(0.1, 0.0, Status::INEXACT),
(-0.1, -1.0, Status::INEXACT),
(0.9, 0.0, Status::INEXACT),
(-0.9, -1.0, Status::INEXACT),
(1.1, 1.0, Status::INEXACT),
(-1.1, -2.0, Status::INEXACT),
(1.9, 1.0, Status::INEXACT),
(-1.9, -2.0, Status::INEXACT),
];
spec_test::<f32>(&cases);
}
#[test]
fn sanity_check_f64() {
assert_eq!(floor(1.1f64), 1.0);
assert_eq!(floor(2.9f64), 2.0);
}
#[test]
fn spec_tests_f64() {
let cases = [
(0.1, 0.0, Status::INEXACT),
(-0.1, -1.0, Status::INEXACT),
(0.9, 0.0, Status::INEXACT),
(-0.9, -1.0, Status::INEXACT),
(1.1, 1.0, Status::INEXACT),
(-1.1, -2.0, Status::INEXACT),
(1.9, 1.0, Status::INEXACT),
(-1.9, -2.0, Status::INEXACT),
];
spec_test::<f64>(&cases);
fn check_f64() {
check::<f64>(&cases!(f64));
check::<f64>(&[
(hf64!("0x1p52"), hf64!("0x1p52"), Status::OK),
(hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK),
]);
}
#[test]
#[cfg(f128_enabled)]
fn spec_tests_f128() {
let cases = [];
spec_test::<f128>(&cases);
check::<f128>(&cases!(f128));
check::<f128>(&[
(hf128!("0x1p112"), hf128!("0x1p112"), Status::OK),
(hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK),
]);
}
}
@@ -8,11 +8,15 @@
//! - Otherwise, either `x` or `y`, canonicalized
//! - -0.0 and +0.0 may be disregarded (unlike newer operations)
//!
//! Excluded from our implementation is sNaN handling.
//! We do not treat sNaN and qNaN differently; even though IEEE technically requires this, (a call
//! with sNaN should return qNaN rather than the other result), it breaks associativity so isn't
//! desired behavior. C23 does not differentiate between sNaN and qNaN, so we do not either. More
//! on the problems with `minNum` [here][minnum-removal].
//!
//! More on the differences: [link].
//! IEEE also specifies that a sNaN in either argument should signal invalid, but we do not
//! implement this.
//!
//! [link]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf
//! [minnum-removal]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf
use crate::support::Float;
@@ -7,7 +7,8 @@
//! - +0.0 if x and y are zero with opposite signs
//! - qNaN if either operation is NaN
//!
//! Excluded from our implementation is sNaN handling.
//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid,
//! but we do not implement this.
use crate::support::Float;
@@ -9,7 +9,8 @@
//! - Non-NaN if one operand is NaN
//! - qNaN if both operands are NaNx
//!
//! Excluded from our implementation is sNaN handling.
//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid,
//! but we do not implement this.
use crate::support::Float;
@@ -8,11 +8,15 @@
//! - Otherwise, either `x` or `y`, canonicalized
//! - -0.0 and +0.0 may be disregarded (unlike newer operations)
//!
//! Excluded from our implementation is sNaN handling.
//! We do not treat sNaN and qNaN differently; even though IEEE technically requires this, (a call
//! with sNaN should return qNaN rather than the other result), it breaks associativity so isn't
//! desired behavior. C23 does not differentiate between sNaN and qNaN, so we do not either. More
//! on the problems with `minNum` [here][minnum-removal].
//!
//! More on the differences: [link].
//! IEEE also specifies that a sNaN in either argument should signal invalid, but we do not
//! implement this.
//!
//! [link]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf
//! [minnum-removal]: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf
use crate::support::Float;
@@ -7,7 +7,8 @@
//! - -0.0 if x and y are zero with opposite signs
//! - qNaN if either operation is NaN
//!
//! Excluded from our implementation is sNaN handling.
//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid,
//! but we do not implement this.
use crate::support::Float;
@@ -9,7 +9,8 @@
//! - Non-NaN if one operand is NaN
//! - qNaN if both operands are NaNx
//!
//! Excluded from our implementation is sNaN handling.
//! Note that the IEEE 754-2019 specifies that a sNaN in either argument should signal invalid,
//! but we do not implement this.
use crate::support::Float;
@@ -0,0 +1,26 @@
use super::super::{CastFrom, Float};
#[inline]
pub fn frexp<F: Float>(x: F) -> (F, i32) {
let mut ix = x.to_bits();
let mut ee = x.ex() as i32;
if ee == 0 {
if x == F::ZERO {
return (x, 0);
}
// Subnormals, needs to be normalized first
ix &= F::SIG_MASK;
(ee, ix) = F::normalize(ix);
ix |= x.to_bits() & F::SIGN_MASK;
} else if ee == F::EXP_SAT as i32 {
// inf or NaN
return (x, 0);
}
let e = ee - (F::EXP_BIAS as i32 - 1);
ix &= F::SIGN_MASK | F::SIG_MASK;
ix |= F::Int::cast_from(F::EXP_BIAS - 1) << F::SIG_BITS;
(F::from_bits(ix), e)
}
@@ -0,0 +1,35 @@
use super::super::{Float, MinInt};
const FP_ILOGBNAN: i32 = i32::MIN;
const FP_ILOGB0: i32 = FP_ILOGBNAN;
#[inline]
pub fn ilogb<F: Float>(x: F) -> i32 {
let zero = F::Int::ZERO;
let mut i = x.to_bits();
let e = x.ex() as i32;
if e == 0 {
i <<= F::EXP_BITS + 1;
if i == F::Int::ZERO {
force_eval!(0.0 / 0.0);
return FP_ILOGB0;
}
/* subnormal x */
let mut e = -(F::EXP_BIAS as i32);
while i >> (F::BITS - 1) == zero {
e -= 1;
i <<= 1;
}
e
} else if e == F::EXP_SAT as i32 {
force_eval!(0.0 / 0.0);
if i << (F::EXP_BITS + 1) != zero {
FP_ILOGBNAN
} else {
i32::MAX
}
} else {
e - F::EXP_BIAS as i32
}
}
@@ -15,6 +15,8 @@
mod fminimum;
mod fminimum_num;
mod fmod;
mod frexp;
mod ilogb;
mod rint;
mod round;
mod scalbn;
@@ -35,6 +37,8 @@
pub use fminimum::fminimum;
pub use fminimum_num::fminimum_num;
pub use fmod::fmod;
pub use frexp::frexp;
pub use ilogb::ilogb;
pub use rint::rint_round;
pub use round::round;
pub use scalbn::scalbn;
@@ -10,38 +10,34 @@ pub fn trunc<F: Float>(x: F) -> F {
#[inline]
pub fn trunc_status<F: Float>(x: F) -> FpResult<F> {
let mut xi: F::Int = x.to_bits();
let xi: F::Int = x.to_bits();
let e: i32 = x.exp_unbiased();
// C1: The represented value has no fractional part, so no truncation is needed
// The represented value has no fractional part, so no truncation is needed
if e >= F::SIG_BITS as i32 {
return FpResult::ok(x);
}
let mask = if e < 0 {
// C2: If the exponent is negative, the result will be zero so we mask out everything
let clear_mask = if e < 0 {
// If the exponent is negative, the result will be zero so we clear everything
// except the sign.
F::SIGN_MASK
!F::SIGN_MASK
} else {
// C3: Otherwise, we mask out the last `e` bits of the significand.
!(F::SIG_MASK >> e.unsigned())
// Otherwise, we keep `e` fractional bits and clear the rest.
F::SIG_MASK >> e.unsigned()
};
// C4: If the to-be-masked-out portion is already zero, we have an exact result
if (xi & !mask) == IntTy::<F>::ZERO {
return FpResult::ok(x);
}
// C5: Otherwise the result is inexact and we will truncate. Raise `FE_INEXACT`, mask the
// result, and return.
let status = if xi & F::SIG_MASK == F::Int::ZERO {
let cleared = xi & clear_mask;
let status = if cleared == IntTy::<F>::ZERO {
// If the to-be-zeroed portion is already zero, we have an exact result.
Status::OK
} else {
// Otherwise the result is inexact and we will truncate, so indicate `FE_INEXACT`.
Status::INEXACT
};
xi &= mask;
FpResult::new(F::from_bits(xi), status)
// Now zero the bits we need to truncate and return.
FpResult::new(F::from_bits(xi ^ cleared), status)
}
#[cfg(test)]
@@ -49,100 +45,83 @@ mod tests {
use super::*;
use crate::support::Hexf;
fn spec_test<F: Float>(cases: &[(F, F, Status)]) {
let roundtrip = [
F::ZERO,
F::ONE,
F::NEG_ONE,
F::NEG_ZERO,
F::INFINITY,
F::NEG_INFINITY,
];
for x in roundtrip {
let FpResult { val, status } = trunc_status(x);
assert_biteq!(val, x, "{}", Hexf(x));
assert_eq!(status, Status::OK, "{}", Hexf(x));
}
for &(x, res, res_stat) in cases {
let FpResult { val, status } = trunc_status(x);
assert_biteq!(val, res, "{}", Hexf(x));
assert_eq!(status, res_stat, "{}", Hexf(x));
}
macro_rules! cases {
($f:ty) => {
[
// roundtrip
(0.0, 0.0, Status::OK),
(-0.0, -0.0, Status::OK),
(1.0, 1.0, Status::OK),
(-1.0, -1.0, Status::OK),
(<$f>::INFINITY, <$f>::INFINITY, Status::OK),
(<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK),
// with rounding
(0.1, 0.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.5, 0.0, Status::INEXACT),
(-0.5, -0.0, Status::INEXACT),
(0.9, 0.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 1.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.5, 1.0, Status::INEXACT),
(-1.5, -1.0, Status::INEXACT),
(1.9, 1.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
]
};
}
/* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */
#[track_caller]
fn check<F: Float>(cases: &[(F, F, Status)]) {
for &(x, exp_res, exp_stat) in cases {
let FpResult { val, status } = trunc_status(x);
assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x));
assert_eq!(
status,
exp_stat,
"{x:?} {} -> {exp_res:?} {}",
Hexf(x),
Hexf(exp_res)
);
}
}
#[test]
#[cfg(f16_enabled)]
fn spec_tests_f16() {
let cases = [];
spec_test::<f16>(&cases);
fn check_f16() {
check::<f16>(&cases!(f16));
check::<f16>(&[
(hf16!("0x1p10"), hf16!("0x1p10"), Status::OK),
(hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK),
]);
}
#[test]
fn sanity_check_f32() {
assert_eq!(trunc(0.5f32), 0.0);
assert_eq!(trunc(1.1f32), 1.0);
assert_eq!(trunc(2.9f32), 2.0);
fn check_f32() {
check::<f32>(&cases!(f32));
check::<f32>(&[
(hf32!("0x1p23"), hf32!("0x1p23"), Status::OK),
(hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK),
]);
}
#[test]
fn spec_tests_f32() {
let cases = [
(0.1, 0.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.9, 0.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 1.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.9, 1.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
];
spec_test::<f32>(&cases);
assert_biteq!(trunc(1.1f32), 1.0);
assert_biteq!(trunc(1.1f64), 1.0);
// C1
assert_biteq!(trunc(hf32!("0x1p23")), hf32!("0x1p23"));
assert_biteq!(trunc(hf64!("0x1p52")), hf64!("0x1p52"));
assert_biteq!(trunc(hf32!("-0x1p23")), hf32!("-0x1p23"));
assert_biteq!(trunc(hf64!("-0x1p52")), hf64!("-0x1p52"));
// C2
assert_biteq!(trunc(hf32!("0x1p-1")), 0.0);
assert_biteq!(trunc(hf64!("0x1p-1")), 0.0);
assert_biteq!(trunc(hf32!("-0x1p-1")), -0.0);
assert_biteq!(trunc(hf64!("-0x1p-1")), -0.0);
}
#[test]
fn sanity_check_f64() {
assert_eq!(trunc(1.1f64), 1.0);
assert_eq!(trunc(2.9f64), 2.0);
}
#[test]
fn spec_tests_f64() {
let cases = [
(0.1, 0.0, Status::INEXACT),
(-0.1, -0.0, Status::INEXACT),
(0.9, 0.0, Status::INEXACT),
(-0.9, -0.0, Status::INEXACT),
(1.1, 1.0, Status::INEXACT),
(-1.1, -1.0, Status::INEXACT),
(1.9, 1.0, Status::INEXACT),
(-1.9, -1.0, Status::INEXACT),
];
spec_test::<f64>(&cases);
fn check_f64() {
check::<f64>(&cases!(f64));
check::<f64>(&[
(hf64!("0x1p52"), hf64!("0x1p52"), Status::OK),
(hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK),
]);
}
#[test]
#[cfg(f128_enabled)]
fn spec_tests_f128() {
let cases = [];
spec_test::<f128>(&cases);
check::<f128>(&cases!(f128));
check::<f128>(&[
(hf128!("0x1p112"), hf128!("0x1p112"), Status::OK),
(hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK),
]);
}
}
@@ -1,32 +1,25 @@
const FP_ILOGBNAN: i32 = -1 - 0x7fffffff;
const FP_ILOGB0: i32 = FP_ILOGBNAN;
/// Extract the binary exponent of `x`.
#[cfg(f16_enabled)]
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn ilogbf16(x: f16) -> i32 {
super::generic::ilogb(x)
}
/// Extract the binary exponent of `x`.
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn ilogbf(x: f32) -> i32 {
super::generic::ilogb(x)
}
/// Extract the binary exponent of `x`.
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn ilogb(x: f64) -> i32 {
let mut i: u64 = x.to_bits();
let e = ((i >> 52) & 0x7ff) as i32;
if e == 0 {
i <<= 12;
if i == 0 {
force_eval!(0.0 / 0.0);
return FP_ILOGB0;
}
/* subnormal x */
let mut e = -0x3ff;
while (i >> 63) == 0 {
e -= 1;
i <<= 1;
}
e
} else if e == 0x7ff {
force_eval!(0.0 / 0.0);
if (i << 12) != 0 {
FP_ILOGBNAN
} else {
i32::MAX
}
} else {
e - 0x3ff
}
super::generic::ilogb(x)
}
/// Extract the binary exponent of `x`.
#[cfg(f128_enabled)]
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn ilogbf128(x: f128) -> i32 {
super::generic::ilogb(x)
}
@@ -1,28 +0,0 @@
const FP_ILOGBNAN: i32 = -1 - 0x7fffffff;
const FP_ILOGB0: i32 = FP_ILOGBNAN;
#[cfg_attr(assert_no_panic, no_panic::no_panic)]
pub fn ilogbf(x: f32) -> i32 {
let mut i = x.to_bits();
let e = ((i >> 23) & 0xff) as i32;
if e == 0 {
i <<= 9;
if i == 0 {
force_eval!(0.0 / 0.0);
return FP_ILOGB0;
}
/* subnormal x */
let mut e = -0x7f;
while (i >> 31) == 0 {
e -= 1;
i <<= 1;
}
e
} else if e == 0xff {
force_eval!(0.0 / 0.0);
if (i << 9) != 0 { FP_ILOGBNAN } else { i32::MAX }
} else {
e - 0x7f
}
}
@@ -79,6 +79,7 @@
*/
use super::{floor, k_cos, k_sin, log};
use crate::support::unchecked_div_i32;
const PI: f64 = 3.14159265358979311600e+00; /* 0x400921FB, 0x54442D18 */
const A0: f64 = 7.72156649015328655494e-02; /* 0x3FB3C467, 0xE37DB0C8 */
@@ -152,7 +153,8 @@ fn sin_pi(mut x: f64) -> f64 {
x = 2.0 * (x * 0.5 - floor(x * 0.5)); /* x mod 2.0 */
n = (x * 4.0) as i32;
n = div!(n + 1, 2);
// SAFETY: nonzero divisor, nonnegative dividend (`n < 8`).
n = unsafe { unchecked_div_i32(n + 1, 2) };
x -= (n as f64) * 0.5;
x *= PI;
@@ -14,6 +14,7 @@
*/
use super::{floorf, k_cosf, k_sinf, logf};
use crate::support::unchecked_div_isize;
const PI: f32 = 3.1415927410e+00; /* 0x40490fdb */
const A0: f32 = 7.7215664089e-02; /* 0x3d9e233f */
@@ -88,7 +89,8 @@ fn sin_pi(mut x: f32) -> f32 {
x = 2.0 * (x * 0.5 - floorf(x * 0.5)); /* x mod 2.0 */
n = (x * 4.0) as isize;
n = div!(n + 1, 2);
// SAFETY: nonzero divisor, nonnegative dividend (`n < 8`).
n = unsafe { unchecked_div_isize(n + 1, 2) };
y = (x as f64) - (n as f64) * 0.5;
y *= 3.14159265358979323846;
match n {
+6 -24
View File
@@ -58,24 +58,6 @@ macro_rules! i {
};
}
// Temporary macro to avoid panic codegen for division (in debug mode too). At
// the time of this writing this is only used in a few places, and once
// rust-lang/rust#72751 is fixed then this macro will no longer be necessary and
// the native `/` operator can be used and panics won't be codegen'd.
#[cfg(any(debug_assertions, not(intrinsics_enabled)))]
macro_rules! div {
($a:expr, $b:expr) => {
$a / $b
};
}
#[cfg(all(not(debug_assertions), intrinsics_enabled))]
macro_rules! div {
($a:expr, $b:expr) => {
unsafe { core::intrinsics::unchecked_div($a, $b) }
};
}
// `support` may be public for testing
#[macro_use]
#[cfg(feature = "unstable-public-internals")]
@@ -166,11 +148,9 @@ macro_rules! div {
mod fminimum_fmaximum_num;
mod fmod;
mod frexp;
mod frexpf;
mod hypot;
mod hypotf;
mod ilogb;
mod ilogbf;
mod j0;
mod j0f;
mod j1;
@@ -260,12 +240,10 @@ macro_rules! div {
pub use self::fminimum_fmaximum::{fmaximum, fmaximumf, fminimum, fminimumf};
pub use self::fminimum_fmaximum_num::{fmaximum_num, fmaximum_numf, fminimum_num, fminimum_numf};
pub use self::fmod::{fmod, fmodf};
pub use self::frexp::frexp;
pub use self::frexpf::frexpf;
pub use self::frexp::{frexp, frexpf};
pub use self::hypot::hypot;
pub use self::hypotf::hypotf;
pub use self::ilogb::ilogb;
pub use self::ilogbf::ilogbf;
pub use self::ilogb::{ilogb, ilogbf};
pub use self::j0::{j0, y0};
pub use self::j0f::{j0f, y0f};
pub use self::j1::{j1, y1};
@@ -326,6 +304,8 @@ macro_rules! div {
pub use self::fminimum_fmaximum::{fmaximumf16, fminimumf16};
pub use self::fminimum_fmaximum_num::{fmaximum_numf16, fminimum_numf16};
pub use self::fmod::fmodf16;
pub use self::frexp::frexpf16;
pub use self::ilogb::ilogbf16;
pub use self::ldexp::ldexpf16;
pub use self::rint::rintf16;
pub use self::round::roundf16;
@@ -353,6 +333,8 @@ macro_rules! div {
pub use self::fminimum_fmaximum::{fmaximumf128, fminimumf128};
pub use self::fminimum_fmaximum_num::{fmaximum_numf128, fminimum_numf128};
pub use self::fmod::fmodf128;
pub use self::frexp::frexpf128;
pub use self::ilogb::ilogbf128;
pub use self::ldexp::ldexpf128;
pub use self::rint::rintf128;
pub use self::round::roundf128;
@@ -1,4 +1,6 @@
#![allow(unused_unsafe)]
// FIXME(clippy): the suggested fix is bad but the code as-written could be better
#![allow(clippy::explicit_counter_loop)]
/* origin: FreeBSD /usr/src/lib/msun/src/k_rem_pio2.c */
/*
* ====================================================
@@ -255,7 +257,7 @@ extern "C" fn floor(x: f64) -> f64 {
/* determine jx,jv,q0, note that 3>q0 */
let jx = nx - 1;
let mut jv = div!(e0 - 3, 24);
let mut jv = (e0 - 3) / 24;
if jv < 0 {
jv = 0;
}
@@ -122,8 +122,6 @@ pub fn sincosf(x: f32) -> (f32, f32) {
}
}
// 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::sincosf;
@@ -261,8 +261,6 @@ fn shr_u256() {
#[test]
#[should_panic]
#[cfg(debug_assertions)]
// FIXME(ppc): ppc64le seems to have issues with `should_panic` tests.
#[cfg(not(all(target_arch = "powerpc64", target_endian = "little")))]
fn shr_u256_overflow() {
// Like regular shr, panic on overflow with debug assertions
let _ = u256::MAX >> 256;
@@ -49,10 +49,14 @@ pub enum Round {
}
/// IEEE 754 exception status flags.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Status(u8);
impl Status {
/* Note that if we ever store/load this to/from floating point control status registers, it
* may be worth making these values platform-dependent to line up with register layout
* to avoid bit swapping. For the time being, this isn't a concern. */
/// Default status indicating no errors.
pub const OK: Self = Self(0);
@@ -74,7 +78,7 @@ impl Status {
/// result is -inf.
/// `x / y` when `x != 0.0` and `y == 0.0`,
#[cfg_attr(not(feature = "unstable-public-internals"), allow(dead_code))]
pub const DIVIDE_BY_ZERO: Self = Self(1 << 2);
pub const DIVIDE_BY_ZERO: Self = Self(1 << 1);
/// The result exceeds the maximum finite value.
///
@@ -82,14 +86,14 @@ impl Status {
/// on the intermediate result. `Zero` rounds to the signed maximum finite. `Positive` and
/// `Negative` round to signed maximum finite in one direction, signed infinity in the other.
#[cfg_attr(not(feature = "unstable-public-internals"), allow(dead_code))]
pub const OVERFLOW: Self = Self(1 << 3);
pub const OVERFLOW: Self = Self(1 << 2);
/// The result is subnormal and lost precision.
pub const UNDERFLOW: Self = Self(1 << 4);
pub const UNDERFLOW: Self = Self(1 << 3);
/// The finite-precision result does not match that of infinite precision, and the reason
/// is not represented by one of the other flags.
pub const INEXACT: Self = Self(1 << 5);
pub const INEXACT: Self = Self(1 << 4);
/// True if `UNDERFLOW` is set.
#[cfg_attr(not(feature = "unstable-public-internals"), allow(dead_code))]
@@ -128,3 +132,38 @@ pub(crate) const fn with(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
#[cfg(any(test, feature = "unstable-public-internals"))]
impl core::fmt::Debug for Status {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// shift -> flag map
let names = &[
"INVALID",
"DIVIDE_BY_ZERO",
"OVERFLOW",
"UNDERFLOW",
"INEXACT",
];
write!(f, "Status(")?;
let mut any = false;
for shift in 0..u8::BITS {
if self.0 & (1 << shift) != 0 {
if any {
write!(f, " | ")?;
}
match names.get(shift as usize) {
Some(name) => write!(f, "{name}")?,
None => write!(f, "UNKNOWN(1 << {shift})")?,
}
any = true;
}
}
if !any {
write!(f, "OK")?;
}
write!(f, ")")?;
Ok(())
}
}
@@ -2,6 +2,12 @@
use super::int_traits::{CastFrom, Int, MinInt};
/// Whether MIPS sNaN/qNaNs should be used.
///
/// Note that MIPS is doing some general migration here, though this is only available on (rare)
/// modern MIPS hardware per discussion at <https://github.com/WebAssembly/design/issues/976>.
const MIPS_NAN: bool = cfg!(target_arch = "mips") || cfg!(target_arch = "mips64");
/// Trait for some basic operations on floats
// #[allow(dead_code)]
#[allow(dead_code)] // Some constants are only used with tests
@@ -34,10 +40,16 @@ pub trait Float:
const NEG_ONE: Self;
const INFINITY: Self;
const NEG_INFINITY: Self;
const NAN: Self;
const NEG_NAN: Self;
const MAX: Self;
const MIN: Self;
/// Quiet NaN.
const NAN: Self;
/// Signaling NaN.
const SNAN: Self;
const NEG_NAN: Self;
const NEG_SNAN: Self;
const EPSILON: Self;
const PI: Self;
const NEG_PI: Self;
@@ -84,6 +96,9 @@ pub trait Float:
/// The implicit bit of the float format
const IMPLICIT_BIT: Self::Int;
/// A mask for the top bit of the significand, useful for NaN ops.
const SIG_TOP_BIT: Self::Int;
/// Returns `self` transmuted to `Self::Int`
fn to_bits(self) -> Self::Int;
@@ -116,6 +131,24 @@ fn eq_repr(self, rhs: Self) -> bool {
/// Returns true if the value is NaN.
fn is_nan(self) -> bool;
fn is_qnan(self) -> bool {
if !self.is_nan() {
return false;
}
let top = self.to_bits() & Self::SIG_TOP_BIT;
if MIPS_NAN {
top == Self::Int::ZERO
} else {
top != Self::Int::ZERO
}
}
fn is_snan(self) -> bool {
self.is_nan() && !self.is_qnan()
}
/// Returns true if the value is +inf or -inf.
fn is_infinite(self) -> bool;
@@ -176,7 +209,6 @@ fn from_parts(negative: bool, exponent: u32, significand: Self::Int) -> Self {
fn fma(self, y: Self, z: Self) -> Self;
/// Returns (normalized exponent, normalized significand)
#[allow(dead_code)]
fn normalize(significand: Self::Int) -> (i32, Self::Int);
/// Returns a number that represents the sign of self.
@@ -224,13 +256,28 @@ impl Float for $ty {
const NEG_ONE: Self = -1.0;
const INFINITY: Self = Self::INFINITY;
const NEG_INFINITY: Self = Self::NEG_INFINITY;
const NAN: Self = Self::NAN;
// NAN isn't guaranteed to be positive but it usually is. We only use this for
// tests.
const NEG_NAN: Self = $from_bits($to_bits(Self::NAN) | Self::SIGN_MASK);
const MAX: Self = -Self::MIN;
// Sign bit set, saturated mantissa, saturated exponent with last bit zeroed
const MIN: Self = $from_bits(Self::Int::MAX & !(1 << Self::SIG_BITS));
// The default NaN seems to set one of the top two significand bits on most
// platforms (sNaN vs. qNaN). For mips, the significand is all 1s (with the
// exception of the signaling bit).
const NAN: Self = $from_bits(if MIPS_NAN {
Self::EXP_MASK | (Self::SIG_TOP_BIT - 1)
} else {
Self::EXP_MASK | Self::SIG_TOP_BIT
});
const SNAN: Self = $from_bits(if MIPS_NAN {
Self::EXP_MASK | Self::SIG_MASK
} else {
Self::EXP_MASK | (Self::SIG_TOP_BIT >> 1)
});
// NAN isn't guaranteed to be positive but it usually is. We only use these for
// tests.
const NEG_NAN: Self = $from_bits($to_bits(Self::NAN) | Self::SIGN_MASK);
const NEG_SNAN: Self = $from_bits($to_bits(Self::SNAN) | Self::SIGN_MASK);
const EPSILON: Self = <$ty>::EPSILON;
// Exponent is a 1 in the LSB
@@ -247,6 +294,7 @@ impl Float for $ty {
const SIG_MASK: Self::Int = (1 << Self::SIG_BITS) - 1;
const EXP_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIG_MASK);
const IMPLICIT_BIT: Self::Int = 1 << Self::SIG_BITS;
const SIG_TOP_BIT: Self::Int = Self::IMPLICIT_BIT >> 1;
fn to_bits(self) -> Self::Int {
self.to_bits()
@@ -295,10 +343,7 @@ fn fma(self, y: Self, z: Self) -> Self {
}
fn normalize(significand: Self::Int) -> (i32, Self::Int) {
let shift = significand.leading_zeros().wrapping_sub(Self::EXP_BITS);
(
1i32.wrapping_sub(shift as i32),
significand << shift as Self::Int,
)
(1i32.wrapping_sub(shift as i32), significand << shift)
}
}
};
@@ -454,6 +499,17 @@ fn check_f16() {
assert_eq!(f16::EXP_MAX, 15);
assert_eq!(f16::EXP_MIN, -14);
assert_eq!(f16::EXP_MIN_SUBNORM, -24);
assert_biteq!(f16::NAN, <f16 as Float>::NAN);
// Value of NAN and FLT16_SNAN in C. We don't strictly need to match up, but it is good to
// be aware if there are platforms where we don't.
if MIPS_NAN {
assert_biteq!(f16::NAN, f16::from_bits(0x7fbf));
assert_biteq!(f16::SNAN, f16::from_bits(0x7fff));
} else {
assert_biteq!(f16::NAN, f16::from_bits(0x7e00));
assert_biteq!(f16::SNAN, f16::from_bits(0x7d00));
}
assert!(f16::NAN.is_qnan());
// `exp_unbiased`
assert_eq!(f16::FRAC_PI_2.exp_unbiased(), 0);
@@ -480,6 +536,21 @@ fn check_f32() {
assert_eq!(f32::EXP_MAX, 127);
assert_eq!(f32::EXP_MIN, -126);
assert_eq!(f32::EXP_MIN_SUBNORM, -149);
assert_biteq!(f32::NAN, <f32 as Float>::NAN);
// Value of NAN and FLT_SNAN in C. We don't strictly need to match up, but it is good to
// be aware if there are platforms where we don't.
if MIPS_NAN {
assert_biteq!(f32::NAN, f32::from_bits(0x7fbfffff));
assert_biteq!(f32::SNAN, f32::from_bits(0x7fffffff));
} else {
assert_biteq!(f32::NAN, f32::from_bits(0x7fc00000));
assert_biteq!(f32::SNAN, f32::from_bits(0x7fa00000));
}
assert!(f32::NAN.is_qnan());
// FIXME(rust-lang/rust#115567): x87 use in `is_snan` quiets the sNaN
if !cfg!(x86_no_sse) {
assert!(f32::SNAN.is_snan());
}
// `exp_unbiased`
assert_eq!(f32::FRAC_PI_2.exp_unbiased(), 0);
@@ -510,6 +581,21 @@ fn check_f64() {
assert_eq!(f64::EXP_MAX, 1023);
assert_eq!(f64::EXP_MIN, -1022);
assert_eq!(f64::EXP_MIN_SUBNORM, -1074);
assert_biteq!(f64::NAN, <f64 as Float>::NAN);
// Value of NAN and DBL_SNAN in C. We don't strictly need to match up, but it is good to
// be aware if there are platforms where we don't.
if MIPS_NAN {
assert_biteq!(f64::NAN, f64::from_bits(0x7ff7ffffffffffff));
assert_biteq!(f64::SNAN, f64::from_bits(0x7fffffffffffffff));
} else {
assert_biteq!(f64::NAN, f64::from_bits(0x7ff8000000000000));
assert_biteq!(f64::SNAN, f64::from_bits(0x7ff4000000000000));
}
assert!(f64::NAN.is_qnan());
// FIXME(rust-lang/rust#115567): x87 use in `is_snan` quiets the sNaN
if !cfg!(x86_no_sse) {
assert!(f64::SNAN.is_snan());
}
// `exp_unbiased`
assert_eq!(f64::FRAC_PI_2.exp_unbiased(), 0);
@@ -541,6 +627,30 @@ fn check_f128() {
assert_eq!(f128::EXP_MAX, 16383);
assert_eq!(f128::EXP_MIN, -16382);
assert_eq!(f128::EXP_MIN_SUBNORM, -16494);
assert_biteq!(f128::NAN, <f128 as Float>::NAN);
// Value of NAN and FLT128_SNAN in C. We don't strictly need to match up, but it is good to
// be aware if there are platforms where we don't.
if MIPS_NAN {
assert_biteq!(
f128::NAN,
f128::from_bits(0x7fff7fffffffffffffffffffffffffff)
);
assert_biteq!(
f128::SNAN,
f128::from_bits(0x7fffffffffffffffffffffffffffffff)
);
} else {
assert_biteq!(
f128::NAN,
f128::from_bits(0x7fff8000000000000000000000000000)
);
assert_biteq!(
f128::SNAN,
f128::from_bits(0x7fff4000000000000000000000000000)
);
}
assert!(f128::NAN.is_qnan());
assert!(f128::SNAN.is_snan());
// `exp_unbiased`
assert_eq!(f128::FRAC_PI_2.exp_unbiased(), 0);
@@ -328,7 +328,7 @@ const fn hex_digit(c: u8) -> Option<u8> {
mod hex_fmt {
use core::fmt;
use crate::support::Float;
use crate::support::{Float, Int};
/// Format a floating point number as its IEEE hex (`%a`) representation.
pub struct Hexf<F>(pub F);
@@ -340,8 +340,10 @@ pub(super) fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::R
write!(f, "-")?;
}
if x.is_nan() {
return write!(f, "NaN");
if x.is_snan() {
return write!(f, "sNaN");
} else if x.is_nan() {
return write!(f, "qNaN");
} else if x.is_infinite() {
return write!(f, "inf");
} else if *x == F::ZERO {
@@ -419,7 +421,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _ = f;
unimplemented!()
} else {
fmt::LowerHex::fmt(&self.0, f)
write!(f, "{:#010x}", self.0)
}
}
}
@@ -456,6 +458,53 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
}
}
}
pub struct Hexi<F>(pub F);
impl<I: Int> fmt::LowerHex for Hexi<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "compiler-builtins")] {
let _ = f;
unimplemented!()
} else {
write!(f, "{:#0width$x}", self.0, width = ((I::BITS / 4) + 2) as usize)
}
}
}
}
impl<T> fmt::Debug for Hexi<T>
where
Hexi<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 Hexi<T>
where
Hexi<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"))]
@@ -464,6 +513,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(test)]
mod parse_tests {
extern crate std;
use std::string::String;
use std::{format, println};
use super::*;
@@ -512,6 +562,16 @@ fn rounding_properties(s: &str) -> Result<(), HexFloatParseError> {
}
Ok(())
}
#[cfg_attr(not(f16_enabled), expect(unused))]
pub fn canonicalize_snan_str(s: String) -> String {
if s.contains("sNaN") || s.contains("qNaN") {
s.replace("sNaN", "NaN").replace("qNaN", "NaN")
} else {
s
}
}
#[test]
#[cfg(f16_enabled)]
fn test_rounding() {
@@ -519,7 +579,11 @@ fn test_rounding() {
for i in -n..n {
let u = i.rotate_right(11) as u32;
let s = format!("{}", Hexf(f32::from_bits(u)));
assert!(rounding_properties(&s).is_ok());
let s = canonicalize_snan_str(s);
match rounding_properties(&s) {
Ok(()) => (),
Err(e) => panic!("failed rounding properties for `{s}`: {e:?}"),
}
}
}
@@ -846,8 +910,6 @@ fn test_macros() {
}
#[cfg(test)]
// FIXME(ppc): something with `should_panic` tests cause a SIGILL with ppc64le
#[cfg(not(all(target_arch = "powerpc64", target_endian = "little")))]
mod tests_panicking {
extern crate std;
use super::*;
@@ -1054,8 +1116,11 @@ fn test_f16() {
use std::format;
// Exhaustively check that `f16` roundtrips.
for x in 0..=u16::MAX {
use super::parse_tests::canonicalize_snan_str;
let f = f16::from_bits(x);
let s = format!("{}", Hexf(f));
let s = canonicalize_snan_str(s);
let from_s = hf16(&s);
if f.is_nan() && from_s.is_nan() {
@@ -1074,6 +1139,8 @@ fn test_f16() {
#[cfg(f16_enabled)]
fn test_f16_to_f32() {
use std::format;
use super::parse_tests::canonicalize_snan_str;
// Exhaustively check that these are equivalent for all `f16`:
// - `f16 -> f32`
// - `f16 -> str -> f32`
@@ -1082,8 +1149,10 @@ fn test_f16_to_f32() {
for x in 0..=u16::MAX {
let f16 = f16::from_bits(x);
let s16 = format!("{}", Hexf(f16));
let s16 = canonicalize_snan_str(s16);
let f32 = f16 as f32;
let s32 = format!("{}", Hexf(f32));
let s32 = canonicalize_snan_str(s32);
let a = hf32(&s16);
let b = hf32(&s32);
@@ -1124,8 +1193,17 @@ fn spot_checks() {
assert_eq!(Hexf(f32::NEG_ZERO).to_string(), "-0x0p+0");
assert_eq!(Hexf(f64::NEG_ZERO).to_string(), "-0x0p+0");
assert_eq!(Hexf(f32::NAN).to_string(), "NaN");
assert_eq!(Hexf(f64::NAN).to_string(), "NaN");
assert_eq!(Hexf(f32::NAN).to_string(), "qNaN");
assert_eq!(Hexf(f64::NAN).to_string(), "qNaN");
assert_eq!(Hexf(f32::NEG_NAN).to_string(), "-qNaN");
assert_eq!(Hexf(f64::NEG_NAN).to_string(), "-qNaN");
if !cfg!(x86_no_sse) {
// FIXME(rust-lang/rust#115567): calls quiet the sNaN
assert_eq!(Hexf(f32::SNAN).to_string(), "sNaN");
assert_eq!(Hexf(f64::SNAN).to_string(), "sNaN");
assert_eq!(Hexf(f32::NEG_SNAN).to_string(), "-sNaN");
assert_eq!(Hexf(f64::NEG_SNAN).to_string(), "-sNaN");
}
assert_eq!(Hexf(f32::INFINITY).to_string(), "inf");
assert_eq!(Hexf(f64::INFINITY).to_string(), "inf");
@@ -1139,7 +1217,9 @@ fn spot_checks() {
assert_eq!(Hexf(f16::MIN).to_string(), "-0x1.ffcp+15");
assert_eq!(Hexf(f16::ZERO).to_string(), "0x0p+0");
assert_eq!(Hexf(f16::NEG_ZERO).to_string(), "-0x0p+0");
assert_eq!(Hexf(f16::NAN).to_string(), "NaN");
assert_eq!(Hexf(f16::NAN).to_string(), "qNaN");
assert_eq!(Hexf(f16::SNAN).to_string(), "sNaN");
assert_eq!(Hexf(f16::NEG_NAN).to_string(), "-qNaN");
assert_eq!(Hexf(f16::INFINITY).to_string(), "inf");
assert_eq!(Hexf(f16::NEG_INFINITY).to_string(), "-inf");
}
@@ -1156,7 +1236,9 @@ fn spot_checks() {
);
assert_eq!(Hexf(f128::ZERO).to_string(), "0x0p+0");
assert_eq!(Hexf(f128::NEG_ZERO).to_string(), "-0x0p+0");
assert_eq!(Hexf(f128::NAN).to_string(), "NaN");
assert_eq!(Hexf(f128::NAN).to_string(), "qNaN");
assert_eq!(Hexf(f128::SNAN).to_string(), "sNaN");
assert_eq!(Hexf(f128::NEG_NAN).to_string(), "-qNaN");
assert_eq!(Hexf(f128::INFINITY).to_string(), "inf");
assert_eq!(Hexf(f128::NEG_INFINITY).to_string(), "-inf");
}
@@ -35,5 +35,38 @@
/// Hint to the compiler that the current path is cold.
pub fn cold_path() {
#[cfg(intrinsics_enabled)]
core::intrinsics::cold_path();
core::hint::cold_path();
}
/// # Safety
///
/// `y` must not be zero and the result must not overflow (`x > i32::MIN`).
pub unsafe fn unchecked_div_i32(x: i32, y: i32) -> i32 {
cfg_if! {
if #[cfg(all(not(debug_assertions), intrinsics_enabled))] {
// Temporary macro to avoid panic codegen for division (in debug mode too). At
// the time of this writing this is only used in a few places, and once
// rust-lang/rust#72751 is fixed then this macro will no longer be necessary and
// the native `/` operator can be used and panics won't be codegen'd.
//
// Note: I am not sure whether the above comment is still up to date, we need
// to double check whether panics are elided where we use this.
unsafe { core::intrinsics::unchecked_div(x, y) }
} else {
x / y
}
}
}
/// # Safety
///
/// `y` must not be zero and the result must not overflow (`x > isize::MIN`).
pub unsafe fn unchecked_div_isize(x: isize, y: isize) -> isize {
cfg_if! {
if #[cfg(all(not(debug_assertions), intrinsics_enabled))] {
unsafe { core::intrinsics::unchecked_div(x, y) }
} else {
x / y
}
}
}
@@ -23,6 +23,7 @@
most ideas and constants are from boost and python
*/
use super::{exp, floor, k_cos, k_sin, pow};
use crate::support::unchecked_div_isize;
const PI: f64 = 3.141592653589793238462643383279502884;
@@ -37,7 +38,8 @@ fn sinpi(mut x: f64) -> f64 {
/* reduce x into [-.25,.25] */
n = (4.0 * x) as isize;
n = div!(n + 1, 2);
// SAFETY: nonzero divisor, nonnegative dividend (`n < 8`).
n = unsafe { unchecked_div_isize(n + 1, 2) };
x -= (n as f64) * 0.5;
x *= PI;