diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 3fed58f2a207d..261b1619f1c5f 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -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 diff --git a/library/compiler-builtins/.gitignore b/library/compiler-builtins/.gitignore index abe346659d4c7..1137a19cf093b 100644 --- a/library/compiler-builtins/.gitignore +++ b/library/compiler-builtins/.gitignore @@ -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 diff --git a/library/compiler-builtins/Cargo.lock b/library/compiler-builtins/Cargo.lock new file mode 100644 index 0000000000000..7a3e9a38430b6 --- /dev/null +++ b/library/compiler-builtins/Cargo.lock @@ -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" diff --git a/library/compiler-builtins/Cargo.toml b/library/compiler-builtins/Cargo.toml index 26f67e02fc520..26a056f434967 100644 --- a/library/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/Cargo.toml @@ -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" diff --git a/library/compiler-builtins/builtins-test-intrinsics/src/main.rs b/library/compiler-builtins/builtins-test-intrinsics/src/main.rs index b9d19ea77256e..a3df2c98376cb 100644 --- a/library/compiler-builtins/builtins-test-intrinsics/src/main.rs +++ b/library/compiler-builtins/builtins-test-intrinsics/src/main.rs @@ -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)] diff --git a/library/compiler-builtins/builtins-test/Cargo.toml b/library/compiler-builtins/builtins-test/Cargo.toml index 9395ab1a985e5..b2313bb9d140e 100644 --- a/library/compiler-builtins/builtins-test/Cargo.toml +++ b/library/compiler-builtins/builtins-test/Cargo.toml @@ -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" diff --git a/library/compiler-builtins/builtins-test/src/lib.rs b/library/compiler-builtins/builtins-test/src/lib.rs index f1673133be27d..b9ad649f88dd7 100644 --- a/library/compiler-builtins/builtins-test/src/lib.rs +++ b/library/compiler-builtins/builtins-test/src/lib.rs @@ -22,7 +22,7 @@ extern crate alloc; 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 diff --git a/library/compiler-builtins/builtins-test/tests/addsub.rs b/library/compiler-builtins/builtins-test/tests/addsub.rs index f3334bd0e2d3a..410967967d2a3 100644 --- a/library/compiler-builtins/builtins-test/tests/addsub.rs +++ b/library/compiler-builtins/builtins-test/tests/addsub.rs @@ -1,4 +1,4 @@ -#![allow(unused_macros)] +#![allow(unused_macros, unused_features)] #![cfg_attr(f16_enabled, feature(f16))] #![cfg_attr(f128_enabled, feature(f128))] diff --git a/library/compiler-builtins/builtins-test/tests/conv.rs b/library/compiler-builtins/builtins-test/tests/conv.rs index 9b04295d2efe8..0fd15ad3ee662 100644 --- a/library/compiler-builtins/builtins-test/tests/conv.rs +++ b/library/compiler-builtins/builtins-test/tests/conv.rs @@ -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; diff --git a/library/compiler-builtins/builtins-test/tests/div_rem.rs b/library/compiler-builtins/builtins-test/tests/div_rem.rs index caee4166c9958..4ff86385a9630 100644 --- a/library/compiler-builtins/builtins-test/tests/div_rem.rs +++ b/library/compiler-builtins/builtins-test/tests/div_rem.rs @@ -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}; diff --git a/library/compiler-builtins/builtins-test/tests/float_pow.rs b/library/compiler-builtins/builtins-test/tests/float_pow.rs index a17dff27c105a..3cea83a255c8c 100644 --- a/library/compiler-builtins/builtins-test/tests/float_pow.rs +++ b/library/compiler-builtins/builtins-test/tests/float_pow.rs @@ -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))] diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index 56891be8a8ac1..d408fe193bcff 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -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(n: u32, f: F) +where + ::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 @@ mod cas { ); 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 @@ macro_rules! test_op { ($_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 @@ test_op!(add, |left, right| left.wrapping_add(right)); 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); diff --git a/library/compiler-builtins/builtins-test/tests/mul.rs b/library/compiler-builtins/builtins-test/tests/mul.rs index bbf1157db42f9..30516ebaf7866 100644 --- a/library/compiler-builtins/builtins-test/tests/mul.rs +++ b/library/compiler-builtins/builtins-test/tests/mul.rs @@ -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::*; diff --git a/library/compiler-builtins/ci/bench-runtime.sh b/library/compiler-builtins/ci/bench-walltime.sh similarity index 77% rename from library/compiler-builtins/ci/bench-runtime.sh rename to library/compiler-builtins/ci/bench-walltime.sh index d272cf33463ed..0393d02dfc452 100755 --- a/library/compiler-builtins/ci/bench-runtime.sh +++ b/library/compiler-builtins/ci/bench-walltime.sh @@ -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 diff --git a/library/compiler-builtins/ci/ci-util.py b/library/compiler-builtins/ci/ci-util.py index ef9ce455178ec..392f83c219e7a 100755 --- a/library/compiler-builtins/ci/ci-util.py +++ b/library/compiler-builtins/ci/ci-util.py @@ -11,7 +11,7 @@ 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 pathlib import Path from typing import TypedDict, Self -USAGE = cleandoc( - """ +USAGE = cleandoc(""" usage: ./ci/ci-util.py [flags] @@ -44,8 +43,7 @@ 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\S*)", body, re.MULTILINE) + def __post_init__(self): + directives = re.finditer( + r"^\s*ci:\s*(?P[^\s=]*)(?:\s*=\s*(?P.*))?", + 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 @@ def __init__(self, body: str): 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 @@ def from_pr(cls, pr_number: int | str) -> Self: ) 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 @@ def emit_workflow_output(self): 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 @@ def emit_workflow_output(self): 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. diff --git a/library/compiler-builtins/ci/miri.sh b/library/compiler-builtins/ci/miri.sh index 7b0ea44c690f3..aae474d884638 100755 --- a/library/compiler-builtins/ci/miri.sh +++ b/library/compiler-builtins/ci/miri.sh @@ -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 diff --git a/library/compiler-builtins/ci/run.sh b/library/compiler-builtins/ci/run.sh index 12b3f37889c99..b9a21d555c9e5 100755 --- a/library/compiler-builtins/ci/run.sh +++ b/library/compiler-builtins/ci/run.sh @@ -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. diff --git a/library/compiler-builtins/compiler-builtins/README.md b/library/compiler-builtins/compiler-builtins/README.md index a12bd2ee73499..63cbf33d2aea2 100644 --- a/library/compiler-builtins/compiler-builtins/README.md +++ b/library/compiler-builtins/compiler-builtins/README.md @@ -22,7 +22,7 @@ See `build.rs` for details. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md). +See [CONTRIBUTING.md](../CONTRIBUTING.md). ## Progress diff --git a/library/compiler-builtins/compiler-builtins/src/aarch64.rs b/library/compiler-builtins/compiler-builtins/src/aarch64.rs index 1b230a214eefe..2c12cb7f3095b 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64.rs @@ -1,7 +1,3 @@ -#![allow(unused_imports)] - -use core::intrinsics; - intrinsics! { #[unsafe(naked)] #[cfg(any(all(windows, target_env = "gnu"), target_os = "uefi"))] diff --git a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs index df0cf76502223..100b67150772a 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs @@ -34,14 +34,27 @@ intrinsics! { } } +/// 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: . +#[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: . +/// See: . +#[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); diff --git a/library/compiler-builtins/compiler-builtins/src/lib.rs b/library/compiler-builtins/compiler-builtins/src/lib.rs index 80395a4738eb2..07960222f20f0 100644 --- a/library/compiler-builtins/compiler-builtins/src/lib.rs +++ b/library/compiler-builtins/compiler-builtins/src/lib.rs @@ -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 @@ pub mod arm; #[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")] diff --git a/library/compiler-builtins/compiler-builtins/src/math/mod.rs b/library/compiler-builtins/compiler-builtins/src/math/mod.rs index 62d7296741057..61dfad213cbe3 100644 --- a/library/compiler-builtins/compiler-builtins/src/math/mod.rs +++ b/library/compiler-builtins/compiler-builtins/src/math/mod.rs @@ -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; diff --git a/library/compiler-builtins/compiler-builtins/src/mem/impls.rs b/library/compiler-builtins/compiler-builtins/src/mem/impls.rs index da16dee25ce54..9681f5d6dac6e 100644 --- a/library/compiler-builtins/compiler-builtins/src/mem/impls.rs +++ b/library/compiler-builtins/compiler-builtins/src/mem/impls.rs @@ -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::(); const WORD_MASK: usize = WORD_SIZE - 1; @@ -209,9 +210,10 @@ pub unsafe fn copy_forward(mut dest: *mut u8, mut src: *const u8, mut 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 @@ pub unsafe fn copy_backward(dest: *mut u8, src: *const u8, mut 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(mut s: *mut u8, c: u8, mut 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(mut s: *mut u8, c: u8, mut 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); } diff --git a/library/compiler-builtins/compiler-builtins/src/x86.rs b/library/compiler-builtins/compiler-builtins/src/x86.rs index 1a3c418609451..45f4ba207e6e0 100644 --- a/library/compiler-builtins/compiler-builtins/src/x86.rs +++ b/library/compiler-builtins/compiler-builtins/src/x86.rs @@ -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 diff --git a/library/compiler-builtins/compiler-builtins/src/x86_64.rs b/library/compiler-builtins/compiler-builtins/src/x86_64.rs index 99a527ee9ac5e..a3a5c8f7e10f9 100644 --- a/library/compiler-builtins/compiler-builtins/src/x86_64.rs +++ b/library/compiler-builtins/compiler-builtins/src/x86_64.rs @@ -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 diff --git a/library/compiler-builtins/crates/libm-macros/src/shared.rs b/library/compiler-builtins/crates/libm-macros/src/shared.rs index 1cefe4e8c7ed4..ee1feed7c35eb 100644 --- a/library/compiler-builtins/crates/libm-macros/src/shared.rs +++ b/library/compiler-builtins/crates/libm-macros/src/shared.rs @@ -279,6 +279,17 @@ const ALL_OPERATIONS_NESTED: &[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 @@ const ALL_OPERATIONS_NESTED: &[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 @@ const ALL_OPERATIONS_NESTED: &[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 @@ const ALL_OPERATIONS_NESTED: &[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, diff --git a/library/compiler-builtins/crates/libm-macros/tests/basic.rs b/library/compiler-builtins/crates/libm-macros/tests/basic.rs index b4276262229fe..1876db8f5869d 100644 --- a/library/compiler-builtins/crates/libm-macros/tests/basic.rs +++ b/library/compiler-builtins/crates/libm-macros/tests/basic.rs @@ -1,3 +1,4 @@ +#![allow(unused_features)] #![feature(f16)] #![feature(f128)] // `STATUS_DLL_NOT_FOUND` on i686 MinGW, not worth looking into. diff --git a/library/compiler-builtins/crates/symbol-check/Cargo.toml b/library/compiler-builtins/crates/symbol-check/Cargo.toml index 5bc13d337c274..6d13f94888004 100644 --- a/library/compiler-builtins/crates/symbol-check/Cargo.toml +++ b/library/compiler-builtins/crates/symbol-check/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" publish = false [dependencies] +getopts.workspace = true object.workspace = true regex.workspace = true serde_json.workspace = true diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 733d9f4e8befb..135019e5f729e 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -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::>(); - let args_ref = args.iter().map(String::as_str).collect::>(); + let mut opts = getopts::Options::new(); + + // 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); + }); - 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); - } + 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::>(); + 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>(paths: &[P]) { +fn check_paths>(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>(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::>().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, p_flags: U32| { + 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, + // . 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 @@ impl BinFile { 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 + } } diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index 400469a49e2a5..cfc8641aca82a 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -2,10 +2,10 @@ use std::env; 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 @@ impl AssertExt for Assert { #[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 = 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); +} diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c b/library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c new file mode 100644 index 0000000000000..dba75b58af1a1 --- /dev/null +++ b/library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c @@ -0,0 +1,3 @@ +/* empty main used to test binaries with compiler options */ + +int main() {} diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/good.rs b/library/compiler-builtins/crates/symbol-check/tests/input/good_lib.rs similarity index 100% rename from library/compiler-builtins/crates/symbol-check/tests/input/good.rs rename to library/compiler-builtins/crates/symbol-check/tests/input/good_lib.rs diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c b/library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c new file mode 100644 index 0000000000000..d4be217b5a06c --- /dev/null +++ b/library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c @@ -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); +} diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S b/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S new file mode 100644 index 0000000000000..09bb761c40ba7 --- /dev/null +++ b/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S @@ -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 diff --git a/library/compiler-builtins/crates/util/Cargo.toml b/library/compiler-builtins/crates/util/Cargo.toml index c56e2cc12ea58..b0a365e4735b0 100644 --- a/library/compiler-builtins/crates/util/Cargo.toml +++ b/library/compiler-builtins/crates/util/Cargo.toml @@ -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 diff --git a/library/compiler-builtins/crates/util/src/main.rs b/library/compiler-builtins/crates/util/src/main.rs index 5972181531b26..70aa613f18d06 100644 --- a/library/compiler-builtins/crates/util/src/main.rs +++ b/library/compiler-builtins/crates/util/src/main.rs @@ -8,10 +8,11 @@ use std::env; 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 @@ cargo run -p util -- SUBCOMMAND: eval inputs... + x 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::>(); 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(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 @@ macro_rules! impl_parse_tuple_via_rug { }; } +#[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); diff --git a/library/compiler-builtins/etc/function-definitions.json b/library/compiler-builtins/etc/function-definitions.json index 4f796905b7543..6bd395a84b66f 100644 --- a/library/compiler-builtins/etc/function-definitions.json +++ b/library/compiler-builtins/etc/function-definitions.json @@ -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" diff --git a/library/compiler-builtins/etc/function-list.txt b/library/compiler-builtins/etc/function-list.txt index 1f226c8c0ff3b..f7a694d10f95a 100644 --- a/library/compiler-builtins/etc/function-list.txt +++ b/library/compiler-builtins/etc/function-list.txt @@ -84,10 +84,14 @@ fmodf128 fmodf16 frexp frexpf +frexpf128 +frexpf16 hypot hypotf ilogb ilogbf +ilogbf128 +ilogbf16 j0 j0f j1 diff --git a/library/compiler-builtins/libm-test/Cargo.toml b/library/compiler-builtins/libm-test/Cargo.toml index 4f65504bd584f..8a8c2b0a2ce01 100644 --- a/library/compiler-builtins/libm-test/Cargo.toml +++ b/library/compiler-builtins/libm-test/Cargo.toml @@ -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 diff --git a/library/compiler-builtins/libm-test/benches/icount.rs b/library/compiler-builtins/libm-test/benches/icount.rs index fb856d9be4517..617e9fb7ad21f 100644 --- a/library/compiler-builtins/libm-test/benches/icount.rs +++ b/library/compiler-builtins/libm-test/benches/icount.rs @@ -331,10 +331,14 @@ main!( 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, diff --git a/library/compiler-builtins/libm-test/src/f8_impl.rs b/library/compiler-builtins/libm-test/src/f8_impl.rs index 905c7d7fde92a..9f19f518e2a34 100644 --- a/library/compiler-builtins/libm-test/src/f8_impl.rs +++ b/library/compiler-builtins/libm-test/src/f8_impl.rs @@ -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 diff --git a/library/compiler-builtins/libm-test/src/generate/case_list.rs b/library/compiler-builtins/libm-test/src/generate/case_list.rs index 43b28722f2dd2..66d7f6a282f6b 100644 --- a/library/compiler-builtins/libm-test/src/generate/case_list.rs +++ b/library/compiler-builtins/libm-test/src/generate/case_list.rs @@ -460,7 +460,8 @@ fn fmodf16_cases() -> Vec> { vec![] } -fn frexp_cases() -> Vec> { +#[cfg(f16_enabled)] +fn frexpf16_cases() -> Vec> { vec![] } @@ -468,6 +469,15 @@ fn frexpf_cases() -> Vec> { vec![] } +fn frexp_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn frexpf128_cases() -> Vec> { + vec![] +} + fn hypot_cases() -> Vec> { vec![] } @@ -476,7 +486,8 @@ fn hypotf_cases() -> Vec> { vec![] } -fn ilogb_cases() -> Vec> { +#[cfg(f16_enabled)] +fn ilogbf16_cases() -> Vec> { vec![] } @@ -484,6 +495,15 @@ fn ilogbf_cases() -> Vec> { vec![] } +fn ilogb_cases() -> Vec> { + vec![] +} + +#[cfg(f128_enabled)] +fn ilogbf128_cases() -> Vec> { + vec![] +} + fn j0_cases() -> Vec> { vec![] } diff --git a/library/compiler-builtins/libm-test/src/generate/random.rs b/library/compiler-builtins/libm-test/src/generate/random.rs index 4ee88946d8eaf..09a3766c66780 100644 --- a/library/compiler-builtins/libm-test/src/generate/random.rs +++ b/library/compiler-builtins/libm-test/src/generate/random.rs @@ -5,7 +5,7 @@ use std::sync::LazyLock; 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; diff --git a/library/compiler-builtins/libm-test/src/mpfloat.rs b/library/compiler-builtins/libm-test/src/mpfloat.rs index 85f0a4da4a6e2..91130f892b8ab 100644 --- a/library/compiler-builtins/libm-test/src/mpfloat.rs +++ b/library/compiler-builtins/libm-test/src/mpfloat.rs @@ -162,8 +162,12 @@ libm_macros::for_each_function! { fmodf16, frexp, frexpf, + frexpf128, + frexpf16, ilogb, ilogbf, + ilogbf128, + ilogbf16, jn, jnf, ldexp, @@ -324,43 +328,6 @@ macro_rules! impl_op_for_ty { } } - impl MpOp for crate::op::[]::Routine { - type MpTy = MpFloat; - - fn new_mp() -> Self::MpTy { - new_mpfloat::() - } - - fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { - this.assign(input.0); - let exp = this.frexp_mut(); - (prep_retval::(this, Ordering::Equal), exp) - } - } - - impl MpOp for crate::op::[]::Routine { - type MpTy = MpFloat; - - fn new_mp() -> Self::MpTy { - new_mpfloat::() - } - - 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::[]::Routine { type MpTy = MpFloat; @@ -505,6 +472,43 @@ macro_rules! impl_op_for_ty_all { } } + impl MpOp for crate::op::[]::Routine { + type MpTy = MpFloat; + + fn new_mp() -> Self::MpTy { + new_mpfloat::() + } + + fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { + this.assign(input.0); + let exp = this.frexp_mut(); + (prep_retval::(this, Ordering::Equal), exp) + } + } + + impl MpOp for crate::op::[]::Routine { + type MpTy = MpFloat; + + fn new_mp() -> Self::MpTy { + new_mpfloat::() + } + + 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::[]::Routine { diff --git a/library/compiler-builtins/libm-test/src/precision.rs b/library/compiler-builtins/libm-test/src/precision.rs index 5d52da168fe72..e994244142749 100644 --- a/library/compiler-builtins/libm-test/src/precision.rs +++ b/library/compiler-builtins/libm-test/src/precision.rs @@ -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 @@ impl MaybeOverride<(f32,)> for SpecialCase { 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 diff --git a/library/compiler-builtins/libm-test/src/test_traits.rs b/library/compiler-builtins/libm-test/src/test_traits.rs index 278274d917b35..f8621a3734a4c 100644 --- a/library/compiler-builtins/libm-test/src/test_traits.rs +++ b/library/compiler-builtins/libm-test/src/test_traits.rs @@ -260,7 +260,7 @@ where 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 @@ impl_tuples!( (f32, f32); (f64, f64); ); + +#[cfg(f16_enabled)] +impl_tuples!( + (f16, i32); + (f16, f16); +); + +#[cfg(f128_enabled)] +impl_tuples!( + (f128, i32); + (f128, f128); +); diff --git a/library/compiler-builtins/libm-test/tests/u256.rs b/library/compiler-builtins/libm-test/tests/u256.rs index d1c5cfbcc586d..e697945f47971 100644 --- a/library/compiler-builtins/libm-test/tests/u256.rs +++ b/library/compiler-builtins/libm-test/tests/u256.rs @@ -10,7 +10,7 @@ type BigInt = rug::Integer; 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; diff --git a/library/compiler-builtins/libm/Cargo.toml b/library/compiler-builtins/libm/Cargo.toml index 98202d1977dc6..28e594dca1f91 100644 --- a/library/compiler-builtins/libm/Cargo.toml +++ b/library/compiler-builtins/libm/Cargo.toml @@ -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"] diff --git a/library/compiler-builtins/libm/src/libm_helper.rs b/library/compiler-builtins/libm/src/libm_helper.rs index 0bb669398657e..1e6e0beb3e3e0 100644 --- a/library/compiler-builtins/libm/src/libm_helper.rs +++ b/library/compiler-builtins/libm/src/libm_helper.rs @@ -202,6 +202,8 @@ libm_helper! { (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 @@ libm_helper! { (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); diff --git a/library/compiler-builtins/libm/src/math/exp2.rs b/library/compiler-builtins/libm/src/math/exp2.rs index 08b71587f6de5..d4c9e96652000 100644 --- a/library/compiler-builtins/libm/src/math/exp2.rs +++ b/library/compiler-builtins/libm/src/math/exp2.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/fmin_fmax.rs b/library/compiler-builtins/libm/src/math/fmin_fmax.rs index c4c1b0435dd29..ead9e6599f1be 100644 --- a/library/compiler-builtins/libm/src/math/fmin_fmax.rs +++ b/library/compiler-builtins/libm/src/math/fmin_fmax.rs @@ -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: 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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { 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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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] diff --git a/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs b/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs index a3c9c9c3991b7..ffc724e3a8d74 100644 --- a/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs +++ b/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs @@ -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: impl Fn(F, F) -> F) { @@ -122,29 +123,63 @@ mod tests { (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 @@ mod tests { (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] diff --git a/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs b/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs index 612cefe756e30..3157f8a3fee8c 100644 --- a/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs +++ b/library/compiler-builtins/libm/src/math/fminimum_fmaximum_num.rs @@ -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: impl Fn(F, F) -> F) { @@ -81,6 +82,8 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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 @@ mod tests { (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] diff --git a/library/compiler-builtins/libm/src/math/frexp.rs b/library/compiler-builtins/libm/src/math/frexp.rs index 932111eebc955..af38915a3ac69 100644 --- a/library/compiler-builtins/libm/src/math/frexp.rs +++ b/library/compiler-builtins/libm/src/math/frexp.rs @@ -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 frexp(x: f64) -> (f64, i32) { - let mut y = x.to_bits(); - let ee = ((y >> 52) & 0x7ff) as i32; +pub fn frexpf16(x: f16) -> (f16, i32) { + super::generic::frexp(x) +} - 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); - } +/// 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) +} - let e = ee - 0x3fe; - y &= 0x800fffffffffffff; - y |= 0x3fe0000000000000; - return (f64::from_bits(y), e); +/// 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) { + 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) } diff --git a/library/compiler-builtins/libm/src/math/frexpf.rs b/library/compiler-builtins/libm/src/math/frexpf.rs deleted file mode 100644 index 904bf14f7b8ea..0000000000000 --- a/library/compiler-builtins/libm/src/math/frexpf.rs +++ /dev/null @@ -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) -} diff --git a/library/compiler-builtins/libm/src/math/generic/ceil.rs b/library/compiler-builtins/libm/src/math/generic/ceil.rs index 1072ba7c29b63..5584f6503ef58 100644 --- a/library/compiler-builtins/libm/src/math/generic/ceil.rs +++ b/library/compiler-builtins/libm/src/math/generic/ceil.rs @@ -46,7 +46,7 @@ pub fn ceil_status(x: F) -> FpResult { 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(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)); - } + 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), + ] + }; + } - for &(x, res, res_stat) in cases { + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { let FpResult { val, status } = ceil_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); } } - /* Skipping f16 / f128 "sanity_check"s due to rejected literal lexing at MSRV */ - #[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::(&cases); - } - - #[test] - fn sanity_check_f32() { - assert_eq!(ceil(1.1f32), 2.0); - assert_eq!(ceil(2.9f32), 3.0); - } - - #[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::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f64() { - assert_eq!(ceil(1.1f64), 2.0); - assert_eq!(ceil(2.9f64), 3.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[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::(&cases); + fn check_f64() { + check::(&cases!(f64)); + check::(&[ + (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::(&cases); + check::(&cases!(f128)); + check::(&[ + (hf128!("0x1p112"), hf128!("0x1p112"), Status::OK), + (hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK), + ]); } } diff --git a/library/compiler-builtins/libm/src/math/generic/floor.rs b/library/compiler-builtins/libm/src/math/generic/floor.rs index e6dfd8866a425..7045229c0c75a 100644 --- a/library/compiler-builtins/libm/src/math/generic/floor.rs +++ b/library/compiler-builtins/libm/src/math/generic/floor.rs @@ -26,7 +26,6 @@ pub fn floor_status(x: F) -> FpResult { 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(x: F) -> FpResult { 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(x: F) -> FpResult { 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(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)); - } + 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), + ] + }; + } - for &(x, res, res_stat) in cases { + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { let FpResult { val, status } = floor_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); } } - /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */ - #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = []; - spec_test::(&cases); - } - - #[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); - } - - #[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::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f64() { - assert_eq!(floor(1.1f64), 1.0); - assert_eq!(floor(2.9f64), 2.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[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::(&cases); + fn check_f64() { + check::(&cases!(f64)); + check::(&[ + (hf64!("0x1p52"), hf64!("0x1p52"), Status::OK), + (hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK), + ]); } #[test] #[cfg(f128_enabled)] fn spec_tests_f128() { - let cases = []; - spec_test::(&cases); + check::(&cases!(f128)); + check::(&[ + (hf128!("0x1p112"), hf128!("0x1p112"), Status::OK), + (hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK), + ]); } } diff --git a/library/compiler-builtins/libm/src/math/generic/fmax.rs b/library/compiler-builtins/libm/src/math/generic/fmax.rs index b05804704d03e..bdd46cc38a818 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmax.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmax.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/generic/fmaximum.rs b/library/compiler-builtins/libm/src/math/generic/fmaximum.rs index 55a031e18ee8d..1fa48f964804e 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmaximum.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmaximum.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs b/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs index 2dc60b2d237f5..c7ca50a89dc8d 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmaximum_num.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/generic/fmin.rs b/library/compiler-builtins/libm/src/math/generic/fmin.rs index e2245bf9e137b..e7755e82345c6 100644 --- a/library/compiler-builtins/libm/src/math/generic/fmin.rs +++ b/library/compiler-builtins/libm/src/math/generic/fmin.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/generic/fminimum.rs b/library/compiler-builtins/libm/src/math/generic/fminimum.rs index aa68b1291d42b..82e72d6440649 100644 --- a/library/compiler-builtins/libm/src/math/generic/fminimum.rs +++ b/library/compiler-builtins/libm/src/math/generic/fminimum.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs b/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs index 265bd4605ce39..5b5271b123bad 100644 --- a/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs +++ b/library/compiler-builtins/libm/src/math/generic/fminimum_num.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/generic/frexp.rs b/library/compiler-builtins/libm/src/math/generic/frexp.rs new file mode 100644 index 0000000000000..cbf5b100ba0f8 --- /dev/null +++ b/library/compiler-builtins/libm/src/math/generic/frexp.rs @@ -0,0 +1,26 @@ +use super::super::{CastFrom, Float}; + +#[inline] +pub fn frexp(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) +} diff --git a/library/compiler-builtins/libm/src/math/generic/ilogb.rs b/library/compiler-builtins/libm/src/math/generic/ilogb.rs new file mode 100644 index 0000000000000..02a7d6b30b103 --- /dev/null +++ b/library/compiler-builtins/libm/src/math/generic/ilogb.rs @@ -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(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 + } +} diff --git a/library/compiler-builtins/libm/src/math/generic/mod.rs b/library/compiler-builtins/libm/src/math/generic/mod.rs index 9d497a03f5447..114fcddf516e5 100644 --- a/library/compiler-builtins/libm/src/math/generic/mod.rs +++ b/library/compiler-builtins/libm/src/math/generic/mod.rs @@ -15,6 +15,8 @@ mod fmin; mod fminimum; mod fminimum_num; mod fmod; +mod frexp; +mod ilogb; mod rint; mod round; mod scalbn; @@ -35,6 +37,8 @@ pub use fmin::fmin; 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; diff --git a/library/compiler-builtins/libm/src/math/generic/trunc.rs b/library/compiler-builtins/libm/src/math/generic/trunc.rs index d5b444d15dfc0..7f18eb42e884a 100644 --- a/library/compiler-builtins/libm/src/math/generic/trunc.rs +++ b/library/compiler-builtins/libm/src/math/generic/trunc.rs @@ -10,38 +10,34 @@ pub fn trunc(x: F) -> F { #[inline] pub fn trunc_status(x: F) -> FpResult { - 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::::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::::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(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)); - } + 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), + ] + }; + } - for &(x, res, res_stat) in cases { + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { let FpResult { val, status } = trunc_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); } } - /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */ - #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = []; - spec_test::(&cases); - } - - #[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); - } - - #[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::(&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); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f64() { - assert_eq!(trunc(1.1f64), 1.0); - assert_eq!(trunc(2.9f64), 2.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[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::(&cases); + fn check_f64() { + check::(&cases!(f64)); + check::(&[ + (hf64!("0x1p52"), hf64!("0x1p52"), Status::OK), + (hf64!("-0x1p52"), hf64!("-0x1p52"), Status::OK), + ]); } #[test] #[cfg(f128_enabled)] fn spec_tests_f128() { - let cases = []; - spec_test::(&cases); + check::(&cases!(f128)); + check::(&[ + (hf128!("0x1p112"), hf128!("0x1p112"), Status::OK), + (hf128!("-0x1p112"), hf128!("-0x1p112"), Status::OK), + ]); } } diff --git a/library/compiler-builtins/libm/src/math/ilogb.rs b/library/compiler-builtins/libm/src/math/ilogb.rs index ef774f6ad3a65..4c3f0a6aa0452 100644 --- a/library/compiler-builtins/libm/src/math/ilogb.rs +++ b/library/compiler-builtins/libm/src/math/ilogb.rs @@ -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; + super::generic::ilogb(x) +} - 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 - } +/// 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) } diff --git a/library/compiler-builtins/libm/src/math/ilogbf.rs b/library/compiler-builtins/libm/src/math/ilogbf.rs deleted file mode 100644 index 5b0cb46ec558b..0000000000000 --- a/library/compiler-builtins/libm/src/math/ilogbf.rs +++ /dev/null @@ -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 - } -} diff --git a/library/compiler-builtins/libm/src/math/lgamma_r.rs b/library/compiler-builtins/libm/src/math/lgamma_r.rs index 38eb270f68390..f3c6fef1464d8 100644 --- a/library/compiler-builtins/libm/src/math/lgamma_r.rs +++ b/library/compiler-builtins/libm/src/math/lgamma_r.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/lgammaf_r.rs b/library/compiler-builtins/libm/src/math/lgammaf_r.rs index a0b6a678a6709..5c087f14d7df2 100644 --- a/library/compiler-builtins/libm/src/math/lgammaf_r.rs +++ b/library/compiler-builtins/libm/src/math/lgammaf_r.rs @@ -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 { diff --git a/library/compiler-builtins/libm/src/math/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index 8eecfe5667d1f..4bee4478164a0 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -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 @@ mod fminimum_fmaximum; 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 @@ pub use self::fmin_fmax::{fmax, fmaxf, fmin, fminf}; 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 @@ cfg_if! { 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 @@ cfg_if! { 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; diff --git a/library/compiler-builtins/libm/src/math/rem_pio2_large.rs b/library/compiler-builtins/libm/src/math/rem_pio2_large.rs index bb2c532916b2a..841a51b84c278 100644 --- a/library/compiler-builtins/libm/src/math/rem_pio2_large.rs +++ b/library/compiler-builtins/libm/src/math/rem_pio2_large.rs @@ -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 @@ pub(crate) fn rem_pio2_large(x: &[f64], y: &mut [f64], e0: i32, prec: usize) -> /* 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; } diff --git a/library/compiler-builtins/libm/src/math/sincosf.rs b/library/compiler-builtins/libm/src/math/sincosf.rs index c4beb5267f280..1d15abe543067 100644 --- a/library/compiler-builtins/libm/src/math/sincosf.rs +++ b/library/compiler-builtins/libm/src/math/sincosf.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/support/big/tests.rs b/library/compiler-builtins/libm/src/math/support/big/tests.rs index 0c32f445c1368..2eafed50a2757 100644 --- a/library/compiler-builtins/libm/src/math/support/big/tests.rs +++ b/library/compiler-builtins/libm/src/math/support/big/tests.rs @@ -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; diff --git a/library/compiler-builtins/libm/src/math/support/env.rs b/library/compiler-builtins/libm/src/math/support/env.rs index 53ae32f658dbe..0f89799ed9189 100644 --- a/library/compiler-builtins/libm/src/math/support/env.rs +++ b/library/compiler-builtins/libm/src/math/support/env.rs @@ -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 @@ impl Status { 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(()) + } +} diff --git a/library/compiler-builtins/libm/src/math/support/float_traits.rs b/library/compiler-builtins/libm/src/math/support/float_traits.rs index 60c8bfca5165b..944546601c9ca 100644 --- a/library/compiler-builtins/libm/src/math/support/float_traits.rs +++ b/library/compiler-builtins/libm/src/math/support/float_traits.rs @@ -2,6 +2,12 @@ use core::{fmt, mem, ops}; 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 . +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 @@ pub trait Float: /// 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 @@ pub trait Float: 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 @@ macro_rules! float_impl { 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 @@ macro_rules! float_impl { 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 @@ macro_rules! float_impl { } 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 @@ mod tests { assert_eq!(f16::EXP_MAX, 15); assert_eq!(f16::EXP_MIN, -14); assert_eq!(f16::EXP_MIN_SUBNORM, -24); + assert_biteq!(f16::NAN, ::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 @@ mod tests { assert_eq!(f32::EXP_MAX, 127); assert_eq!(f32::EXP_MIN, -126); assert_eq!(f32::EXP_MIN_SUBNORM, -149); + assert_biteq!(f32::NAN, ::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 @@ mod tests { assert_eq!(f64::EXP_MAX, 1023); assert_eq!(f64::EXP_MIN, -1022); assert_eq!(f64::EXP_MIN_SUBNORM, -1074); + assert_biteq!(f64::NAN, ::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 @@ mod tests { assert_eq!(f128::EXP_MAX, 16383); assert_eq!(f128::EXP_MIN, -16382); assert_eq!(f128::EXP_MIN_SUBNORM, -16494); + assert_biteq!(f128::NAN, ::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); diff --git a/library/compiler-builtins/libm/src/math/support/hex_float.rs b/library/compiler-builtins/libm/src/math/support/hex_float.rs index 2f9369e504417..5be0d3159de3b 100644 --- a/library/compiler-builtins/libm/src/math/support/hex_float.rs +++ b/library/compiler-builtins/libm/src/math/support/hex_float.rs @@ -328,7 +328,7 @@ const fn hex_digit(c: u8) -> Option { 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(pub F); @@ -340,8 +340,10 @@ mod hex_fmt { 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 @@ mod hex_fmt { let _ = f; unimplemented!() } else { - fmt::LowerHex::fmt(&self.0, f) + write!(f, "{:#010x}", self.0) } } } @@ -456,6 +458,53 @@ mod hex_fmt { } } } + + pub struct Hexi(pub F); + + impl fmt::LowerHex for Hexi { + 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 fmt::Debug for Hexi + where + Hexi: 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 fmt::Display for Hexi + where + Hexi: 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 @@ pub use hex_fmt::*; #[cfg(test)] mod parse_tests { extern crate std; + use std::string::String; use std::{format, println}; use super::*; @@ -512,6 +562,16 @@ mod parse_tests { } 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 @@ mod parse_tests { 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 @@ mod parse_tests { } #[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 @@ mod print_tests { 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 @@ mod print_tests { #[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 @@ mod print_tests { 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 @@ mod print_tests { 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 @@ mod print_tests { 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 @@ mod print_tests { ); 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"); } diff --git a/library/compiler-builtins/libm/src/math/support/mod.rs b/library/compiler-builtins/libm/src/math/support/mod.rs index 15ab010dc8d5f..9dc872cdc1506 100644 --- a/library/compiler-builtins/libm/src/math/support/mod.rs +++ b/library/compiler-builtins/libm/src/math/support/mod.rs @@ -35,5 +35,38 @@ pub use modular::linear_mul_reduction; /// 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 + } + } } diff --git a/library/compiler-builtins/libm/src/math/tgamma.rs b/library/compiler-builtins/libm/src/math/tgamma.rs index 41415d9d12589..c526842470c85 100644 --- a/library/compiler-builtins/libm/src/math/tgamma.rs +++ b/library/compiler-builtins/libm/src/math/tgamma.rs @@ -23,6 +23,7 @@ Gamma(x)*Gamma(-x) = -pi/(x sin(pi x)) 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;