From 0d2a5d24d3ca2416eebb7000608a1ce176025302 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 05:24:36 -0600 Subject: [PATCH 01/50] symcheck: Switch to getopts for argument parsing It would be nice to support a few more flags here, but the current implementation is a bit limited. Switch to `getopts` which should make things a bit easier in the future. --- library/compiler-builtins/Cargo.toml | 1 + library/compiler-builtins/ci/run.sh | 16 ++-- .../crates/symbol-check/Cargo.toml | 1 + .../crates/symbol-check/src/main.rs | 80 ++++++++++++------- .../crates/symbol-check/tests/all.rs | 6 +- 5 files changed, 64 insertions(+), 40 deletions(-) diff --git a/library/compiler-builtins/Cargo.toml b/library/compiler-builtins/Cargo.toml index 26f67e02fc52..3dff23d28577 100644 --- a/library/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/Cargo.toml @@ -37,6 +37,7 @@ assert_cmd = "2.1.2" cc = "1.2.55" compiler_builtins = { path = "builtins-shim", default-features = false } criterion = { version = "0.6.0", default-features = false, features = ["cargo_bench_support"] } +getopts = "0.2.24" getrandom = "0.3.4" gmp-mpfr-sys = { version = "1.6.8", default-features = false } gungraun = "0.17.0" diff --git a/library/compiler-builtins/ci/run.sh b/library/compiler-builtins/ci/run.sh index 12b3f37889c9..04e44e1cb859 100755 --- a/library/compiler-builtins/ci/run.sh +++ b/library/compiler-builtins/ci/run.sh @@ -48,21 +48,21 @@ 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 +"${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/crates/symbol-check/Cargo.toml b/library/compiler-builtins/crates/symbol-check/Cargo.toml index 5bc13d337c27..6d13f9488800 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 733d9f4e8bef..85aa7118aac2 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -8,7 +8,7 @@ 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 object::read::archive::ArchiveFile; @@ -24,38 +24,60 @@ 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] -- 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(); - match &args_ref[1..] { - ["build-and-check", target, "--", args @ ..] if !args.is_empty() => { - run_build_and_check(target, args); - } - ["build-and-check", "--", args @ ..] if !args.is_empty() => { - run_build_and_check(env!("HOST"), args); - } - ["check", paths @ ..] if !paths.is_empty() => { - check_paths(paths); - } - _ => { - println!("{USAGE}"); - std::process::exit(1); + // Ideally these would be subcommands but that isn't supported. + opts.optflag("h", "help", "Print this help message"); + opts.optflag( + "", + "build-and-check", + "Cargo will get invoked with `CARGO_BUILD_ARGS` and the specified target. All output \ + `compiler_builtins*.rlib` files will be checked.", + ); + opts.optopt( + "", + "target", + "Set the target for build-and-check. Falls back to the host target otherwise.", + "TARGET", + ); + opts.optflag( + "", + "check", + "Run checks on the given set of paths, without invoking Cargo. Paths \ + may be either archives or object files.", + ); + + let print_usage_and_exit = |code: i32| -> ! { + eprintln!("{}", opts.usage(USAGE)); + exit(code); + }; + + let m = opts.parse(std::env::args().skip(1)).unwrap_or_else(|e| { + eprintln!("{e}"); + print_usage_and_exit(1); + }); + + if m.opt_present("help") { + print_usage_and_exit(0); + } + + let free_args = m.free.iter().map(String::as_str).collect::>(); + + if m.opt_present("build-and-check") { + let target = m.opt_str("target").unwrap_or(env!("HOST").to_string()); + run_build_and_check(&target, &free_args); + } else if m.opt_present("check") { + if free_args.is_empty() { + print_usage_and_exit(1); } + check_paths(&free_args); + } else { + print_usage_and_exit(1); } } @@ -65,7 +87,7 @@ fn run_build_and_check(target: &str, args: &[&str]) { for arg in args { assert!( !arg.contains("--target"), - "target must be passed positionally. {USAGE}" + "target must be passed to symbol-check" ); } diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index 400469a49e2a..34a2dd3c399e 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -51,7 +51,7 @@ fn test_duplicates() { let status = ar.arg(&dup_out).status().unwrap(); assert!(status.success()); - let assert = cargo_bin_cmd!().arg("check").arg(&lib_out).assert(); + let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); assert .failure() .stderr_contains("duplicate symbols") @@ -65,7 +65,7 @@ fn test_core_symbols() { 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(); + let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); assert .failure() .stderr_contains("found 1 undefined symbols from core") @@ -77,7 +77,7 @@ fn test_good() { 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(); + let assert = cargo_bin_cmd!().arg("--check").arg(&lib_out).assert(); assert.success(); } From 1da633ca4bcc0bb5ffb2a758ff6b9cb6bc78454f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 02/50] symcheck: Always check args, simplify running functions --- .../crates/symbol-check/src/main.rs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 85aa7118aac2..2520073de099 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -67,10 +67,17 @@ fn main() { } let free_args = m.free.iter().map(String::as_str).collect::>(); + for arg in &free_args { + assert!( + !arg.contains("--target"), + "target must be passed to symbol-check" + ); + } if m.opt_present("build-and-check") { let target = m.opt_str("target").unwrap_or(env!("HOST").to_string()); - run_build_and_check(&target, &free_args); + let paths = exec_cargo_with_args(&target, &free_args); + check_paths(&paths); } else if m.opt_present("check") { if free_args.is_empty() { print_usage_and_exit(1); @@ -81,20 +88,6 @@ fn main() { } } -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 { - assert!( - !arg.contains("--target"), - "target must be passed to symbol-check" - ); - } - - let paths = exec_cargo_with_args(target, args); - check_paths(&paths); -} - fn check_paths>(paths: &[P]) { for path in paths { let path = path.as_ref(); From 7d5135c25b4be6355de910dbe352b18fb435d713 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 03/50] symcheck: Clean up test setup that requires environment --- .../crates/symbol-check/tests/all.rs | 103 +++++++++++------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index 34a2dd3c399e..f65038c62da6 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -2,7 +2,6 @@ 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; @@ -13,6 +12,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 +22,19 @@ fn stderr_contains(self, s: &str) -> Self { #[test] fn test_duplicates() { + let t = TestTarget::from_env(); let dir = tempdir().unwrap(); let dup_out = dir.path().join("dup.o"); let lib_out = dir.path().join("libfoo.rlib"); // For the "bad" file, we need duplicate symbols from different object files in the archive. Do // this reliably by building an archive and a separate object file then merging them. - rustc_build(&input_dir().join("duplicates.rs"), &lib_out, |cmd| cmd); - rustc_build(&input_dir().join("duplicates.rs"), &dup_out, |cmd| { + t.rustc_build(&input_dir().join("duplicates.rs"), &lib_out, |cmd| cmd); + t.rustc_build(&input_dir().join("duplicates.rs"), &dup_out, |cmd| { cmd.arg("--emit=obj") }); - let mut ar = cc_build().get_archiver(); + let mut ar = t.cc_build().get_archiver(); if ar.get_program().to_string_lossy().contains("lib.exe") { let mut out_arg = OsString::from("-out:"); @@ -48,10 +49,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 +63,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 +75,24 @@ fn test_core_symbols() { } #[test] -fn test_good() { +fn test_good_lib() { + let t = TestTarget::from_env(); 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.rs"), &lib_out, |cmd| cmd); + let assert = t.symcheck_exe().arg(&lib_out).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()); +/// 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, } -/// 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 -} - -/// 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 +100,50 @@ 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"); + cmd + } } 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); +} From 44b0bd98fd0be6bd0dd5dcea63620c0a41dda8ba Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 04/50] symcheck: Don't check for empty symbol tables with PE binaries PE executables don't seem to have anything in the symbol table. Don't assert that we find any symbols in this case, which allows using symcheck for executable binaries. --- .../crates/symbol-check/src/main.rs | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 2520073de099..683bba285cde 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -13,8 +13,8 @@ use object::read::archive::ArchiveFile; use object::{ - File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, Symbol, SymbolKind, - SymbolScope, + BinaryFormat, File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, + Symbol, SymbolKind, SymbolScope, }; use regex::Regex; use serde_json::Value; @@ -259,7 +259,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(); @@ -300,7 +302,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)); @@ -376,4 +380,23 @@ fn for_each_symbol(&self, mut f: impl FnMut(Symbol, &ObjFile, &str)) { obj.symbols().for_each(|sym| f(sym, &obj, obj_path)); }); } + + /// PE executable files don't have the same kind of symbol tables. This isn't a perfectly + /// accurate check, but at least tells us whether we can skip erroring if we don't find any + /// symbols. + fn has_symbol_tables(&self) -> bool { + let mut empty = true; + let mut ret = false; + + self.for_each_object(|obj, _obj_path| { + if !matches!(obj.format(), BinaryFormat::Pe) { + // Any non-PE objects should have symbol tables. + ret = true; + } + empty = false; + }); + + // If empty, assume there should be tables. + empty || ret + } } From 15d02fc0d71f86fe09a882ed16a541b3f4de4ca0 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 13:16:50 -0600 Subject: [PATCH 05/50] symcheck: Check for binaries requiring a writeable + executable stack Implement the following logic: * For elf executable binaries, check for PT_GNU_STACK with PF_X. This combination tells the kernel to make the stack executable. * For elf intermediate objects, check for `.note.GNU-stack` with SHF_EXECINSTR. This combination in an object file tells the linker to give the final binary PT_GNU_STACK PF_X. * For elf intermediate objects with no `.note.GNU-stack`, assume the legacy behavior that assumes an executable stack is required. * For non-elf binaries, don't check anything. In a follow up it may be possible to check for `MH_ALLOW_STACK_EXECUTION` on Mach-O binaries, but it doesn't seem possible to get the latest compiler to emit this. This appears to match what is done by `scanelf` to emit `!WX` [1], which seems to be the tool used to create the output in the issue. The ld manpage [2] also has some useful notes about these flags, as does the presentation at [3]. Closes: https://github.com/rust-lang/compiler-builtins/issues/183 [1]: https://github.com/gentoo/pax-utils/blob/9ef54b472e42ba2c5479fbd86b8be2275724b064/scanelf.c [2]: https://man7.org/linux/man-pages/man1/ld.1.html [3]: https://www.ndss-symposium.org/wp-content/uploads/6D-s0924-ye.pdf --- library/compiler-builtins/ci/run.sh | 3 + .../crates/symbol-check/src/main.rs | 194 +++++++++++++++++- .../crates/symbol-check/tests/all.rs | 179 +++++++++++++++- .../symbol-check/tests/input/good_bin.c | 3 + .../tests/input/{good.rs => good_lib.rs} | 0 .../tests/input/has_exe_gnu_stack_section.c | 16 ++ .../tests/input/missing_gnu_stack_section.S | 19 ++ 7 files changed, 406 insertions(+), 8 deletions(-) create mode 100644 library/compiler-builtins/crates/symbol-check/tests/input/good_bin.c rename library/compiler-builtins/crates/symbol-check/tests/input/{good.rs => good_lib.rs} (100%) create mode 100644 library/compiler-builtins/crates/symbol-check/tests/input/has_exe_gnu_stack_section.c create mode 100644 library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S diff --git a/library/compiler-builtins/ci/run.sh b/library/compiler-builtins/ci/run.sh index 04e44e1cb859..b9a21d555c9e 100755 --- a/library/compiler-builtins/ci/run.sh +++ b/library/compiler-builtins/ci/run.sh @@ -50,6 +50,9 @@ SYMCHECK_TEST_TARGET="$target" cargo test -p symbol-check --release symcheck=(cargo run -p symbol-check --release) symcheck+=(-- --build-and-check --target "$target") +# 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 diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 683bba285cde..e15522d223d8 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -5,26 +5,27 @@ //! 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, exit}; use std::sync::LazyLock; +use std::{env, fs}; use object::read::archive::ArchiveFile; use object::{ - BinaryFormat, 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 TARGET] -- CARGO_BUILD_ARGS ... + symbol-check --build-and-check [--target TARGET] [--no-os] -- CARGO_BUILD_ARGS ... symbol-check --check PATHS ...\ "; @@ -51,6 +52,12 @@ fn main() { "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.", + ); let print_usage_and_exit = |code: i32| -> ! { eprintln!("{}", opts.usage(USAGE)); @@ -66,6 +73,7 @@ fn main() { print_usage_and_exit(0); } + let no_os_target = m.opt_present("no-os"); let free_args = m.free.iter().map(String::as_str).collect::>(); for arg in &free_args { assert!( @@ -77,18 +85,18 @@ fn main() { 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); + check_paths(&paths, no_os_target); } else if m.opt_present("check") { if free_args.is_empty() { print_usage_and_exit(1); } - check_paths(&free_args); + check_paths(&free_args, no_os_target); } else { print_usage_and_exit(1); } } -fn check_paths>(paths: &[P]) { +fn check_paths>(paths: &[P], no_os_target: bool) { for path in paths { let path = path.as_ref(); println!("Checking {}", path.display()); @@ -96,6 +104,7 @@ fn check_paths>(paths: &[P]) { verify_no_duplicates(&archive); verify_core_symbols(&archive); + verify_no_exec_stack(&archive, no_os_target); } } @@ -320,6 +329,177 @@ fn verify_core_symbols(archive: &BinFile) { println!(" success: no undefined references to core 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, diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index f65038c62da6..6dc34c3e3dba 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -5,6 +5,7 @@ use assert_cmd::assert::Assert; use assert_cmd::cargo::cargo_bin_cmd; +use object::BinaryFormat; use tempfile::tempdir; trait AssertExt { @@ -74,16 +75,134 @@ fn test_core_symbols() { .stderr_contains("from_utf8"); } +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).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).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.rs"), &lib_out, |cmd| cmd); + t.rustc_build(&input_dir().join("good_lib.rs"), &lib_out, |cmd| cmd); let assert = t.symcheck_exe().arg(&lib_out).assert(); assert.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).assert(); + assert.success(); +} + /// 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 { @@ -131,8 +250,66 @@ fn cc_build(&self) -> cc::Build { 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 { 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 000000000000..dba75b58af1a --- /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 000000000000..d4be217b5a06 --- /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 000000000000..09bb761c40ba --- /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 From 200968c81db4920f6d2f1546c476a00ab6ea98af Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 20:55:17 -0600 Subject: [PATCH 06/50] ci: Pin Miri to the 2026-02-11 nightly CI is failing with: error: failed to run custom build command for `quote v1.0.44` Caused by: process didn't exit successfully: `/home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner /home/runner/work/compiler-builtins/compiler-builtins/target/miri/debug/build/quote-32cc00414fa6f72d/build-script-build` (exit status: 1) --- stderr fatal error: file "/home/runner/work/compiler-builtins/compiler-builtins/target/miri/debug/build/quote-32cc00414fa6f72d/build-script-build" contains outdated or invalid JSON; try `cargo clean` Link: https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/build-script-build.20contains.20outdated.20or.20invalid.20JSON/with/573426109 --- library/compiler-builtins/.github/workflows/main.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 3fed58f2a207..a62f847082ae 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -303,7 +303,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 From 8f0208ecb2365135ae3f8a8f67544c43002ae8f4 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 02:58:18 +0000 Subject: [PATCH 07/50] Revert "ci: Temporarily disable native PPC and s390x jobs" These were disabled due to permission issues, which should now be resolved. This reverts commit 28e0556a7bca6ca828186d9de74d30c7cb45db5e. --- .../compiler-builtins/.github/workflows/main.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index a62f847082ae..163b64616b0b 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -70,14 +70,14 @@ 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 + # FIXME(rust#151807): remove once PPC builds work again. + channel: nightly-2026-01-23 - 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 From 804a1f87cfcce416cdfd10830e500ce5f50b4997 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 03:59:46 +0000 Subject: [PATCH 08/50] Revert "ci: Pin rustc on the native PowerPC job" The nightly has the LLVM bump that resolves the relevant issue. This reverts commit 99d1fc7752b0e09cff5729cd4f3783d23c23cb66. Link: https://github.com/rust-lang/rust/pull/152428 --- library/compiler-builtins/.github/workflows/main.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 163b64616b0b..0375d1eb29de 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -72,8 +72,6 @@ jobs: 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: riscv64gc-unknown-linux-gnu os: ubuntu-24.04 - target: s390x-unknown-linux-gnu From f2bec36b92b9e3ba64d8b5b6ad390e534af64f24 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 20 Apr 2025 04:21:18 +0000 Subject: [PATCH 09/50] libm: Convert `frexp` and `ilogb` to a generic implementations --- .../etc/function-definitions.json | 10 ++++-- .../compiler-builtins/libm/src/math/frexp.rs | 30 +++++++--------- .../compiler-builtins/libm/src/math/frexpf.rs | 22 ------------ .../libm/src/math/generic/frexp.rs | 24 +++++++++++++ .../libm/src/math/generic/ilogb.rs | 35 +++++++++++++++++++ .../libm/src/math/generic/mod.rs | 4 +++ .../compiler-builtins/libm/src/math/ilogb.rs | 35 ++++--------------- .../compiler-builtins/libm/src/math/ilogbf.rs | 28 --------------- .../compiler-builtins/libm/src/math/mod.rs | 8 ++--- 9 files changed, 91 insertions(+), 105 deletions(-) delete mode 100644 library/compiler-builtins/libm/src/math/frexpf.rs create mode 100644 library/compiler-builtins/libm/src/math/generic/frexp.rs create mode 100644 library/compiler-builtins/libm/src/math/generic/ilogb.rs delete mode 100644 library/compiler-builtins/libm/src/math/ilogbf.rs diff --git a/library/compiler-builtins/etc/function-definitions.json b/library/compiler-builtins/etc/function-definitions.json index 4f796905b754..105d0266fc60 100644 --- a/library/compiler-builtins/etc/function-definitions.json +++ b/library/compiler-builtins/etc/function-definitions.json @@ -560,13 +560,15 @@ }, "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" }, @@ -584,13 +586,15 @@ }, "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" }, diff --git a/library/compiler-builtins/libm/src/math/frexp.rs b/library/compiler-builtins/libm/src/math/frexp.rs index 932111eebc95..8281ed27bd5d 100644 --- a/library/compiler-builtins/libm/src/math/frexp.rs +++ b/library/compiler-builtins/libm/src/math/frexp.rs @@ -1,21 +1,15 @@ +/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. +/// +/// That is, `x * 2^p` will represent the input value. +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn frexpf(x: f32) -> (f32, i32) { + super::generic::frexp(x) +} + +/// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. +/// +/// That is, `x * 2^p` will represent the input value. #[cfg_attr(assert_no_panic, no_panic::no_panic)] pub fn frexp(x: f64) -> (f64, i32) { - let mut y = x.to_bits(); - let ee = ((y >> 52) & 0x7ff) as i32; - - if ee == 0 { - if x != 0.0 { - let x1p64 = f64::from_bits(0x43f0000000000000); - let (x, e) = frexp(x * x1p64); - return (x, e - 64); - } - return (x, 0); - } else if ee == 0x7ff { - return (x, 0); - } - - let e = ee - 0x3fe; - y &= 0x800fffffffffffff; - y |= 0x3fe0000000000000; - return (f64::from_bits(y), e); + super::generic::frexp(x) } 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 904bf14f7b8e..000000000000 --- 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/frexp.rs b/library/compiler-builtins/libm/src/math/generic/frexp.rs new file mode 100644 index 000000000000..d5c2758a4b04 --- /dev/null +++ b/library/compiler-builtins/libm/src/math/generic/frexp.rs @@ -0,0 +1,24 @@ +use super::super::{CastFrom, Float, MinInt}; + +#[inline] +pub fn frexp(x: F) -> (F, i32) { + let mut ix = x.to_bits(); + let ee = x.ex() as i32; + + if ee == 0 { + if x != F::ZERO { + // normalize via multiplication; 1p64 for `f64` + let magic = F::from_parts(false, F::EXP_BIAS + F::BITS, F::Int::ZERO); + let (x, e) = frexp(x * magic); + return (x, e - F::BITS as i32); + } + return (x, 0); + } else if ee == F::EXP_SAT as i32 { + 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 000000000000..02a7d6b30b10 --- /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 9d497a03f544..114fcddf516e 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 fminimum; mod fminimum_num; mod fmod; +mod frexp; +mod ilogb; mod rint; mod round; mod scalbn; @@ -35,6 +37,8 @@ pub use fminimum::fminimum; pub use fminimum_num::fminimum_num; pub use fmod::fmod; +pub use frexp::frexp; +pub use ilogb::ilogb; pub use rint::rint_round; pub use round::round; pub use scalbn::scalbn; diff --git a/library/compiler-builtins/libm/src/math/ilogb.rs b/library/compiler-builtins/libm/src/math/ilogb.rs index ef774f6ad3a6..13bef5f8c617 100644 --- a/library/compiler-builtins/libm/src/math/ilogb.rs +++ b/library/compiler-builtins/libm/src/math/ilogb.rs @@ -1,32 +1,11 @@ -const FP_ILOGBNAN: i32 = -1 - 0x7fffffff; -const FP_ILOGB0: i32 = FP_ILOGBNAN; +/// Extract the binary exponent of `x`. +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn ilogbf(x: f32) -> i32 { + super::generic::ilogb(x) +} +/// Extract the binary exponent of `x`. #[cfg_attr(assert_no_panic, no_panic::no_panic)] pub fn ilogb(x: f64) -> i32 { - let mut i: u64 = x.to_bits(); - let e = ((i >> 52) & 0x7ff) as i32; - - if e == 0 { - i <<= 12; - if i == 0 { - force_eval!(0.0 / 0.0); - return FP_ILOGB0; - } - /* subnormal x */ - let mut e = -0x3ff; - while (i >> 63) == 0 { - e -= 1; - i <<= 1; - } - e - } else if e == 0x7ff { - force_eval!(0.0 / 0.0); - if (i << 12) != 0 { - FP_ILOGBNAN - } else { - i32::MAX - } - } else { - e - 0x3ff - } + super::generic::ilogb(x) } 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 5b0cb46ec558..000000000000 --- 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/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index 8eecfe5667d1..c1ea60e368d9 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -166,11 +166,9 @@ macro_rules! div { mod fminimum_fmaximum_num; mod fmod; mod frexp; -mod frexpf; mod hypot; mod hypotf; mod ilogb; -mod ilogbf; mod j0; mod j0f; mod j1; @@ -260,12 +258,10 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximum, fmaximumf, fminimum, fminimumf}; pub use self::fminimum_fmaximum_num::{fmaximum_num, fmaximum_numf, fminimum_num, fminimum_numf}; pub use self::fmod::{fmod, fmodf}; -pub use self::frexp::frexp; -pub use self::frexpf::frexpf; +pub use self::frexp::{frexp, frexpf}; pub use self::hypot::hypot; pub use self::hypotf::hypotf; -pub use self::ilogb::ilogb; -pub use self::ilogbf::ilogbf; +pub use self::ilogb::{ilogb, ilogbf}; pub use self::j0::{j0, y0}; pub use self::j0f::{j0f, y0f}; pub use self::j1::{j1, y1}; From 212652c5d4abf5c92c4a654ed9bad1e4dbff809b Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 20 Apr 2025 04:21:18 +0000 Subject: [PATCH 10/50] libm: Add `frexpf128`, `ilogbf16`, and `ilogbf128` The `frexpf16` signature is added (to make traits easier) but left unimplemented, since it will need a slightly different algorithm. --- .../crates/libm-macros/src/shared.rs | 36 +++++++++++ .../etc/function-definitions.json | 21 ++++++ .../compiler-builtins/etc/function-list.txt | 3 + .../libm-test/benches/icount.rs | 3 + .../libm-test/src/generate/case_list.rs | 19 +++++- .../libm-test/src/mpfloat.rs | 64 ++++++++++++------- .../libm-test/src/test_traits.rs | 12 ++++ .../compiler-builtins/libm/src/libm_helper.rs | 4 ++ .../compiler-builtins/libm/src/math/frexp.rs | 20 ++++++ .../compiler-builtins/libm/src/math/ilogb.rs | 14 ++++ .../compiler-builtins/libm/src/math/mod.rs | 6 ++ 11 files changed, 177 insertions(+), 25 deletions(-) diff --git a/library/compiler-builtins/crates/libm-macros/src/shared.rs b/library/compiler-builtins/crates/libm-macros/src/shared.rs index 1cefe4e8c7ed..5a5eca6f685d 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 @@ struct NestedOp { fn_list: &["fmaf128"], public: true, }, + NestedOp { + // `(f16) -> i32` + float_ty: FloatTy::F16, + rust_sig: Signature { + args: &[Ty::F16], + returns: &[Ty::I32], + }, + c_sig: None, + fn_list: &["ilogbf16"], + public: true, + }, NestedOp { // `(f32) -> i32` float_ty: FloatTy::F32, @@ -301,6 +312,17 @@ struct NestedOp { fn_list: &["ilogb"], public: true, }, + NestedOp { + // `(f128) -> i32` + float_ty: FloatTy::F128, + rust_sig: Signature { + args: &[Ty::F128], + returns: &[Ty::I32], + }, + c_sig: None, + fn_list: &["ilogbf128"], + public: true, + }, NestedOp { // `(i32, f32) -> f32` float_ty: FloatTy::F32, @@ -423,6 +445,20 @@ struct NestedOp { fn_list: &["frexp", "lgamma_r"], public: true, }, + NestedOp { + // `(f128, &mut c_int) -> f128` as `(f128) -> (f128, i32)` + float_ty: FloatTy::F128, + rust_sig: Signature { + args: &[Ty::F128], + returns: &[Ty::F128, Ty::I32], + }, + c_sig: Some(Signature { + args: &[Ty::F128, Ty::MutCInt], + returns: &[Ty::F128], + }), + fn_list: &["frexpf128"], + public: true, + }, NestedOp { // `(f32, f32, &mut c_int) -> f32` as `(f32, f32) -> (f32, i32)` float_ty: FloatTy::F32, diff --git a/library/compiler-builtins/etc/function-definitions.json b/library/compiler-builtins/etc/function-definitions.json index 105d0266fc60..06eb1ad5e2e7 100644 --- a/library/compiler-builtins/etc/function-definitions.json +++ b/library/compiler-builtins/etc/function-definitions.json @@ -572,6 +572,13 @@ ], "type": "f32" }, + "frexpf128": { + "sources": [ + "libm/src/math/frexp.rs", + "libm/src/math/generic/frexp.rs" + ], + "type": "f128" + }, "hypot": { "sources": [ "libm/src/math/hypot.rs" @@ -598,6 +605,20 @@ ], "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 1f226c8c0ff3..45a39e3eee8d 100644 --- a/library/compiler-builtins/etc/function-list.txt +++ b/library/compiler-builtins/etc/function-list.txt @@ -84,10 +84,13 @@ fmodf128 fmodf16 frexp frexpf +frexpf128 hypot hypotf ilogb ilogbf +ilogbf128 +ilogbf16 j0 j0f j1 diff --git a/library/compiler-builtins/libm-test/benches/icount.rs b/library/compiler-builtins/libm-test/benches/icount.rs index fb856d9be451..be5178e205a2 100644 --- a/library/compiler-builtins/libm-test/benches/icount.rs +++ b/library/compiler-builtins/libm-test/benches/icount.rs @@ -331,10 +331,13 @@ fn icount_bench_print_hf128(x: f128) -> String { icount_bench_fmodf16_group, icount_bench_fmodf_group, icount_bench_frexp_group, + icount_bench_frexpf128_group, icount_bench_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/generate/case_list.rs b/library/compiler-builtins/libm-test/src/generate/case_list.rs index 43b28722f2dd..958e03ab67f0 100644 --- a/library/compiler-builtins/libm-test/src/generate/case_list.rs +++ b/library/compiler-builtins/libm-test/src/generate/case_list.rs @@ -460,11 +460,16 @@ fn fmodf16_cases() -> Vec> { vec![] } +fn frexpf_cases() -> Vec> { + vec![] +} + fn frexp_cases() -> Vec> { vec![] } -fn frexpf_cases() -> Vec> { +#[cfg(f128_enabled)] +fn frexpf128_cases() -> Vec> { vec![] } @@ -476,7 +481,8 @@ fn hypotf_cases() -> Vec> { vec![] } -fn ilogb_cases() -> Vec> { +#[cfg(f16_enabled)] +fn ilogbf16_cases() -> Vec> { vec![] } @@ -484,6 +490,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/mpfloat.rs b/library/compiler-builtins/libm-test/src/mpfloat.rs index 85f0a4da4a6e..3237a232f7dc 100644 --- a/library/compiler-builtins/libm-test/src/mpfloat.rs +++ b/library/compiler-builtins/libm-test/src/mpfloat.rs @@ -162,8 +162,11 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { fmodf16, frexp, frexpf, + frexpf128, ilogb, ilogbf, + ilogbf128, + ilogbf16, jn, jnf, ldexp, @@ -338,29 +341,6 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } - 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 +485,29 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } + 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 { @@ -546,6 +549,21 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { #[cfg(f128_enabled)] impl_op_for_ty_all!(f128, "f128"); +#[cfg(f128_enabled)] +impl MpOp for crate::op::frexpf128::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) + } +} + // `lgamma_r` is not a simple suffix so we can't use the above macro. impl MpOp for crate::op::lgamma_r::Routine { type MpTy = MpFloat; diff --git a/library/compiler-builtins/libm-test/src/test_traits.rs b/library/compiler-builtins/libm-test/src/test_traits.rs index 278274d917b3..8b21e2af9a04 100644 --- a/library/compiler-builtins/libm-test/src/test_traits.rs +++ b/library/compiler-builtins/libm-test/src/test_traits.rs @@ -456,3 +456,15 @@ fn validate<'a>( (f32, f32); (f64, f64); ); + +#[cfg(f16_enabled)] +impl_tuples!( + (f16, i32); + (f16, f16); +); + +#[cfg(f128_enabled)] +impl_tuples!( + (f128, i32); + (f128, f128); +); diff --git a/library/compiler-builtins/libm/src/libm_helper.rs b/library/compiler-builtins/libm/src/libm_helper.rs index 0bb669398657..1e6e0beb3e3e 100644 --- a/library/compiler-builtins/libm/src/libm_helper.rs +++ b/library/compiler-builtins/libm/src/libm_helper.rs @@ -202,6 +202,8 @@ pub fn $func($($arg: $arg_typ),*) -> ($($ret_typ),*) { (fn fminimum(x: f16, y: f16) -> (f16); => fminimumf16); (fn fminimum_num(x: f16, y: f16) -> (f16); => fminimum_numf16); (fn fmod(x: f16, y: f16) -> (f16); => fmodf16); + (fn frexp(x: f16) -> (f16, i32); => frexpf16); + (fn ilogb(x: f16) -> (i32); => ilogbf16); (fn ldexp(x: f16, n: i32) -> (f16); => ldexpf16); (fn rint(x: f16) -> (f16); => rintf16); (fn round(x: f16) -> (f16); => roundf16); @@ -231,6 +233,8 @@ pub fn $func($($arg: $arg_typ),*) -> ($($ret_typ),*) { (fn fminimum(x: f128, y: f128) -> (f128); => fminimumf128); (fn fminimum_num(x: f128, y: f128) -> (f128); => fminimum_numf128); (fn fmod(x: f128, y: f128) -> (f128); => fmodf128); + (fn frexp(x: f128) -> (f128, i32); => frexpf128); + (fn ilogb(x: f128) -> (i32); => ilogbf128); (fn ldexp(x: f128, n: i32) -> (f128); => ldexpf128); (fn rint(x: f128) -> (f128); => rintf128); (fn round(x: f128) -> (f128); => roundf128); diff --git a/library/compiler-builtins/libm/src/math/frexp.rs b/library/compiler-builtins/libm/src/math/frexp.rs index 8281ed27bd5d..f2f545fb65e4 100644 --- a/library/compiler-builtins/libm/src/math/frexp.rs +++ b/library/compiler-builtins/libm/src/math/frexp.rs @@ -1,3 +1,14 @@ +/// 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. +#[allow(unused)] +#[cfg(f16_enabled)] +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub(crate) fn frexpf16(x: f16) -> (f16, i32) { + unimplemented!() +} + /// 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. @@ -13,3 +24,12 @@ pub fn frexpf(x: f32) -> (f32, i32) { 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/ilogb.rs b/library/compiler-builtins/libm/src/math/ilogb.rs index 13bef5f8c617..4c3f0a6aa045 100644 --- a/library/compiler-builtins/libm/src/math/ilogb.rs +++ b/library/compiler-builtins/libm/src/math/ilogb.rs @@ -1,3 +1,10 @@ +/// 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 { @@ -9,3 +16,10 @@ pub fn ilogbf(x: f32) -> i32 { pub fn ilogb(x: f64) -> i32 { super::generic::ilogb(x) } + +/// Extract the binary exponent of `x`. +#[cfg(f128_enabled)] +#[cfg_attr(assert_no_panic, no_panic::no_panic)] +pub fn ilogbf128(x: f128) -> i32 { + super::generic::ilogb(x) +} diff --git a/library/compiler-builtins/libm/src/math/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index c1ea60e368d9..0484fe6a3765 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -322,6 +322,7 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximumf16, fminimumf16}; pub use self::fminimum_fmaximum_num::{fmaximum_numf16, fminimum_numf16}; pub use self::fmod::fmodf16; + pub use self::ilogb::ilogbf16; pub use self::ldexp::ldexpf16; pub use self::rint::rintf16; pub use self::round::roundf16; @@ -333,6 +334,9 @@ macro_rules! div { #[allow(unused_imports)] pub(crate) use self::fma::fmaf16; + + #[allow(unused_imports)] + pub(crate) use self::frexp::frexpf16; } } @@ -349,6 +353,8 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximumf128, fminimumf128}; pub use self::fminimum_fmaximum_num::{fmaximum_numf128, fminimum_numf128}; pub use self::fmod::fmodf128; + pub use self::frexp::frexpf128; + pub use self::ilogb::ilogbf128; pub use self::ldexp::ldexpf128; pub use self::rint::rintf128; pub use self::round::roundf128; From 0a4613c2978d428ac2ef99d6937a3414cc3c8864 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 22:05:07 -0600 Subject: [PATCH 11/50] util: `f16` now has parsing in the standard library, use it --- library/compiler-builtins/crates/util/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/compiler-builtins/crates/util/src/main.rs b/library/compiler-builtins/crates/util/src/main.rs index 5972181531b2..34e28d7b9759 100644 --- a/library/compiler-builtins/crates/util/src/main.rs +++ b/library/compiler-builtins/crates/util/src/main.rs @@ -232,11 +232,11 @@ fn parse(_input: &[&str]) -> Self { }; } +#[cfg(f16_enabled)] +impl_parse_tuple!(f16); impl_parse_tuple!(f32); impl_parse_tuple!(f64); -#[cfg(f16_enabled)] -impl_parse_tuple_via_rug!(f16); #[cfg(f128_enabled)] impl_parse_tuple_via_rug!(f128); From 6aa1e6932d15709a1fe67df045c7ff6115f6dc9c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 19:56:08 -0600 Subject: [PATCH 12/50] util: Add support for decomposing floats --- library/compiler-builtins/Cargo.toml | 1 + .../compiler-builtins/crates/util/Cargo.toml | 1 + .../compiler-builtins/crates/util/src/main.rs | 70 ++++++++++++++++++- .../libm-test/src/test_traits.rs | 2 +- 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/library/compiler-builtins/Cargo.toml b/library/compiler-builtins/Cargo.toml index 3dff23d28577..785df6a20015 100644 --- a/library/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/Cargo.toml @@ -35,6 +35,7 @@ exclude = [ anyhow = "1.0.101" assert_cmd = "2.1.2" cc = "1.2.55" +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"] } getopts = "0.2.24" diff --git a/library/compiler-builtins/crates/util/Cargo.toml b/library/compiler-builtins/crates/util/Cargo.toml index c56e2cc12ea5..b0a365e4735b 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 34e28d7b9759..498162158426 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::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}; @@ -26,6 +27,10 @@ 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... + For each input, print it in different formats with various floating + point properties (normal, infinite, etc). "; fn main() { @@ -34,6 +39,7 @@ fn main() { match &str_args.as_slice()[1..] { ["eval", 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 +112,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; diff --git a/library/compiler-builtins/libm-test/src/test_traits.rs b/library/compiler-builtins/libm-test/src/test_traits.rs index 8b21e2af9a04..f8621a3734a4 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 @@ fn validate_int(actual: I, expected: I, input: Input, ctx: &CheckCtx) Ok(()) } -impl_int!(u32, i32, u64, i64); +impl_int!(u16, i16, u32, i32, u64, i64, u128, i128); /* trait implementations for floats */ From c1653840a62f6a03ec01db3763cbe3a30c0e2548 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Feb 2026 22:06:49 -0600 Subject: [PATCH 13/50] util: Add an alias for the eval subcommand --- library/compiler-builtins/crates/util/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/compiler-builtins/crates/util/src/main.rs b/library/compiler-builtins/crates/util/src/main.rs index 498162158426..70aa613f18d0 100644 --- a/library/compiler-builtins/crates/util/src/main.rs +++ b/library/compiler-builtins/crates/util/src/main.rs @@ -23,12 +23,14 @@ 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). "; @@ -38,7 +40,7 @@ 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:?}`"); From e7e9546ef6c746055d7b62ccb4b667bab19e925f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 00:41:46 -0600 Subject: [PATCH 14/50] libm: Print `Hexf` with `0x` and zero padding --- library/compiler-builtins/libm/src/math/support/hex_float.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2f9369e50441..4495e3c18801 100644 --- a/library/compiler-builtins/libm/src/math/support/hex_float.rs +++ b/library/compiler-builtins/libm/src/math/support/hex_float.rs @@ -419,7 +419,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _ = f; unimplemented!() } else { - fmt::LowerHex::fmt(&self.0, f) + write!(f, "{:#010x}", self.0) } } } From 4401daa4ff79b1665a95b5546b96356ed8be9a9f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 00:41:46 -0600 Subject: [PATCH 15/50] libm: Switch to a non-recursive algorithm for subnormals This will support `frexpf16` and should allow `generic::frexp` to be inlined into the non-generic `frexp*` functions. Additionally remove an unneeded `as` cast. --- .../libm/src/math/generic/frexp.rs | 18 ++++++++++-------- .../libm/src/math/support/float_traits.rs | 6 +----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/frexp.rs b/library/compiler-builtins/libm/src/math/generic/frexp.rs index d5c2758a4b04..cbf5b100ba0f 100644 --- a/library/compiler-builtins/libm/src/math/generic/frexp.rs +++ b/library/compiler-builtins/libm/src/math/generic/frexp.rs @@ -1,19 +1,21 @@ -use super::super::{CastFrom, Float, MinInt}; +use super::super::{CastFrom, Float}; #[inline] pub fn frexp(x: F) -> (F, i32) { let mut ix = x.to_bits(); - let ee = x.ex() as i32; + let mut ee = x.ex() as i32; if ee == 0 { - if x != F::ZERO { - // normalize via multiplication; 1p64 for `f64` - let magic = F::from_parts(false, F::EXP_BIAS + F::BITS, F::Int::ZERO); - let (x, e) = frexp(x * magic); - return (x, e - F::BITS as i32); + if x == F::ZERO { + return (x, 0); } - 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); } 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 60c8bfca5165..9f2ce532ed17 100644 --- a/library/compiler-builtins/libm/src/math/support/float_traits.rs +++ b/library/compiler-builtins/libm/src/math/support/float_traits.rs @@ -176,7 +176,6 @@ fn from_parts(negative: bool, exponent: u32, significand: Self::Int) -> Self { fn fma(self, y: Self, z: Self) -> Self; /// Returns (normalized exponent, normalized significand) - #[allow(dead_code)] fn normalize(significand: Self::Int) -> (i32, Self::Int); /// Returns a number that represents the sign of self. @@ -295,10 +294,7 @@ fn fma(self, y: Self, z: Self) -> Self { } fn normalize(significand: Self::Int) -> (i32, Self::Int) { let shift = significand.leading_zeros().wrapping_sub(Self::EXP_BITS); - ( - 1i32.wrapping_sub(shift as i32), - significand << shift as Self::Int, - ) + (1i32.wrapping_sub(shift as i32), significand << shift) } } }; From 67ab9a5568209e381b166edd34fbff5c1a26f000 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 00:41:46 -0600 Subject: [PATCH 16/50] libm: Add `frexpf16` The new algorithm now correctly supports the type. --- .../crates/libm-macros/src/shared.rs | 14 ++++++ .../etc/function-definitions.json | 7 +++ .../compiler-builtins/etc/function-list.txt | 1 + .../libm-test/benches/icount.rs | 1 + .../libm-test/src/generate/case_list.rs | 5 +++ .../libm-test/src/mpfloat.rs | 44 +++++++------------ .../compiler-builtins/libm/src/math/frexp.rs | 5 +-- .../compiler-builtins/libm/src/math/mod.rs | 4 +- 8 files changed, 46 insertions(+), 35 deletions(-) diff --git a/library/compiler-builtins/crates/libm-macros/src/shared.rs b/library/compiler-builtins/crates/libm-macros/src/shared.rs index 5a5eca6f685d..ee1feed7c35e 100644 --- a/library/compiler-builtins/crates/libm-macros/src/shared.rs +++ b/library/compiler-builtins/crates/libm-macros/src/shared.rs @@ -417,6 +417,20 @@ struct NestedOp { fn_list: &["modf"], public: true, }, + NestedOp { + // `(f16, &mut c_int) -> f16` as `(f16) -> (f16, i32)` + float_ty: FloatTy::F16, + rust_sig: Signature { + args: &[Ty::F16], + returns: &[Ty::F16, Ty::I32], + }, + c_sig: Some(Signature { + args: &[Ty::F16, Ty::MutCInt], + returns: &[Ty::F16], + }), + fn_list: &["frexpf16"], + public: true, + }, NestedOp { // `(f32, &mut c_int) -> f32` as `(f32) -> (f32, i32)` float_ty: FloatTy::F32, diff --git a/library/compiler-builtins/etc/function-definitions.json b/library/compiler-builtins/etc/function-definitions.json index 06eb1ad5e2e7..6bd395a84b66 100644 --- a/library/compiler-builtins/etc/function-definitions.json +++ b/library/compiler-builtins/etc/function-definitions.json @@ -579,6 +579,13 @@ ], "type": "f128" }, + "frexpf16": { + "sources": [ + "libm/src/math/frexp.rs", + "libm/src/math/generic/frexp.rs" + ], + "type": "f16" + }, "hypot": { "sources": [ "libm/src/math/hypot.rs" diff --git a/library/compiler-builtins/etc/function-list.txt b/library/compiler-builtins/etc/function-list.txt index 45a39e3eee8d..f7a694d10f95 100644 --- a/library/compiler-builtins/etc/function-list.txt +++ b/library/compiler-builtins/etc/function-list.txt @@ -85,6 +85,7 @@ fmodf16 frexp frexpf frexpf128 +frexpf16 hypot hypotf ilogb diff --git a/library/compiler-builtins/libm-test/benches/icount.rs b/library/compiler-builtins/libm-test/benches/icount.rs index be5178e205a2..617e9fb7ad21 100644 --- a/library/compiler-builtins/libm-test/benches/icount.rs +++ b/library/compiler-builtins/libm-test/benches/icount.rs @@ -332,6 +332,7 @@ fn icount_bench_print_hf128(x: f128) -> String { 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, 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 958e03ab67f0..66d7f6a282f6 100644 --- a/library/compiler-builtins/libm-test/src/generate/case_list.rs +++ b/library/compiler-builtins/libm-test/src/generate/case_list.rs @@ -460,6 +460,11 @@ fn fmodf16_cases() -> Vec> { vec![] } +#[cfg(f16_enabled)] +fn frexpf16_cases() -> Vec> { + vec![] +} + fn frexpf_cases() -> Vec> { vec![] } diff --git a/library/compiler-builtins/libm-test/src/mpfloat.rs b/library/compiler-builtins/libm-test/src/mpfloat.rs index 3237a232f7dc..91130f892b8a 100644 --- a/library/compiler-builtins/libm-test/src/mpfloat.rs +++ b/library/compiler-builtins/libm-test/src/mpfloat.rs @@ -163,6 +163,7 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { frexp, frexpf, frexpf128, + frexpf16, ilogb, ilogbf, ilogbf128, @@ -327,20 +328,6 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } - 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; @@ -485,6 +472,20 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { } } + 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; @@ -549,21 +550,6 @@ fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet { #[cfg(f128_enabled)] impl_op_for_ty_all!(f128, "f128"); -#[cfg(f128_enabled)] -impl MpOp for crate::op::frexpf128::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) - } -} - // `lgamma_r` is not a simple suffix so we can't use the above macro. impl MpOp for crate::op::lgamma_r::Routine { type MpTy = MpFloat; diff --git a/library/compiler-builtins/libm/src/math/frexp.rs b/library/compiler-builtins/libm/src/math/frexp.rs index f2f545fb65e4..af38915a3ac6 100644 --- a/library/compiler-builtins/libm/src/math/frexp.rs +++ b/library/compiler-builtins/libm/src/math/frexp.rs @@ -2,11 +2,10 @@ /// /// That is, `x * 2^p` will represent the input value. // Placeholder so we can have `frexpf16` in the `Float` trait. -#[allow(unused)] #[cfg(f16_enabled)] #[cfg_attr(assert_no_panic, no_panic::no_panic)] -pub(crate) fn frexpf16(x: f16) -> (f16, i32) { - unimplemented!() +pub fn frexpf16(x: f16) -> (f16, i32) { + super::generic::frexp(x) } /// Decompose a float into a normalized value within the range `[0.5, 1)`, and a power of 2. diff --git a/library/compiler-builtins/libm/src/math/mod.rs b/library/compiler-builtins/libm/src/math/mod.rs index 0484fe6a3765..b34ab8465349 100644 --- a/library/compiler-builtins/libm/src/math/mod.rs +++ b/library/compiler-builtins/libm/src/math/mod.rs @@ -322,6 +322,7 @@ macro_rules! div { pub use self::fminimum_fmaximum::{fmaximumf16, fminimumf16}; pub use self::fminimum_fmaximum_num::{fmaximum_numf16, fminimum_numf16}; pub use self::fmod::fmodf16; + pub use self::frexp::frexpf16; pub use self::ilogb::ilogbf16; pub use self::ldexp::ldexpf16; pub use self::rint::rintf16; @@ -334,9 +335,6 @@ macro_rules! div { #[allow(unused_imports)] pub(crate) use self::fma::fmaf16; - - #[allow(unused_imports)] - pub(crate) use self::frexp::frexpf16; } } From e68fc280c21da48381fa99f1dc0962625841a60c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 06:17:04 -0600 Subject: [PATCH 17/50] ci: Increase the benchmark rustc version to 2026-02-10 This includes the most recent LLVM bump. --- library/compiler-builtins/.github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 0375d1eb29de..1a69f7484d88 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. From 0b0d40d5f3ab401604d138fdf2cc4785a3ecc6bc Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 07:06:05 -0600 Subject: [PATCH 18/50] libm: Improve debug output for `Status` --- .../libm/src/math/support/env.rs | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/support/env.rs b/library/compiler-builtins/libm/src/math/support/env.rs index 53ae32f658db..0f89799ed918 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 @@ pub(crate) const fn with(self, rhs: Self) -> Self { Self(self.0 | rhs.0) } } + +#[cfg(any(test, feature = "unstable-public-internals"))] +impl core::fmt::Debug for Status { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // shift -> flag map + let names = &[ + "INVALID", + "DIVIDE_BY_ZERO", + "OVERFLOW", + "UNDERFLOW", + "INEXACT", + ]; + + write!(f, "Status(")?; + let mut any = false; + for shift in 0..u8::BITS { + if self.0 & (1 << shift) != 0 { + if any { + write!(f, " | ")?; + } + match names.get(shift as usize) { + Some(name) => write!(f, "{name}")?, + None => write!(f, "UNKNOWN(1 << {shift})")?, + } + any = true; + } + } + + if !any { + write!(f, "OK")?; + } + write!(f, ")")?; + Ok(()) + } +} From 70c6d5a4476db95a94e417694f3535388ef613ed Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 07:06:05 -0600 Subject: [PATCH 19/50] libm: Fix `_status` status outputs on null-mantissa inputs Values like 0.5 that have an exponent but not a mantissa are currently being reported as OK rather than INEXACT for ceil, floor, and trunc. Fix this and clean up the test cases. This tries to keep the algorithm diff as simple as possible, which introduces some small performance regressions. This is improved in a future commit. --- .../libm/src/math/generic/ceil.rs | 146 +++++++--------- .../libm/src/math/generic/floor.rs | 129 +++++++------- .../libm/src/math/generic/trunc.rs | 159 ++++++++---------- 3 files changed, 194 insertions(+), 240 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/ceil.rs b/library/compiler-builtins/libm/src/math/generic/ceil.rs index 1072ba7c29b6..5584f6503ef5 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)); - } - - for &(x, res, res_stat) in cases { - let FpResult { val, status } = ceil_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); - } + macro_rules! cases { + ($f:ty) => { + [ + // roundtrip + (0.0, 0.0, Status::OK), + (-0.0, -0.0, Status::OK), + (1.0, 1.0, Status::OK), + (-1.0, -1.0, Status::OK), + (<$f>::INFINITY, <$f>::INFINITY, Status::OK), + (<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK), + // with rounding + (0.1, 1.0, Status::INEXACT), + (-0.1, -0.0, Status::INEXACT), + (0.5, 1.0, Status::INEXACT), + (-0.5, -0.0, Status::INEXACT), + (0.9, 1.0, Status::INEXACT), + (-0.9, -0.0, Status::INEXACT), + (1.1, 2.0, Status::INEXACT), + (-1.1, -1.0, Status::INEXACT), + (1.5, 2.0, Status::INEXACT), + (-1.5, -1.0, Status::INEXACT), + (1.9, 2.0, Status::INEXACT), + (-1.9, -1.0, Status::INEXACT), + ] + }; } - /* Skipping f16 / f128 "sanity_check"s due to rejected literal lexing at MSRV */ + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { + let FpResult { val, status } = ceil_status(x); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); + } + } #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = [ - (0.1, 1.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 1.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 2.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 2.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f32() { - assert_eq!(ceil(1.1f32), 2.0); - assert_eq!(ceil(2.9f32), 3.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[test] - fn spec_tests_f32() { - let cases = [ - (0.1, 1.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 1.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 2.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 2.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); - } - - #[test] - fn sanity_check_f64() { - assert_eq!(ceil(1.1f64), 2.0); - assert_eq!(ceil(2.9f64), 3.0); - } - - #[test] - fn spec_tests_f64() { - let cases = [ - (0.1, 1.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 1.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 2.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 2.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&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 e6dfd8866a42..c763a3cc265e 100644 --- a/library/compiler-builtins/libm/src/math/generic/floor.rs +++ b/library/compiler-builtins/libm/src/math/generic/floor.rs @@ -46,7 +46,7 @@ pub fn floor_status(x: F) -> FpResult { F::from_bits(ix) } else { // |x| < 1.0, raise an inexact exception since truncation will happen. - if ix & F::SIG_MASK == F::Int::ZERO { + if ix & !F::SIGN_MASK == F::Int::ZERO { status = Status::OK; } else { status = Status::INEXACT; @@ -72,86 +72,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)); - } - - for &(x, res, res_stat) in cases { - let FpResult { val, status } = floor_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); - } + macro_rules! cases { + ($f:ty) => { + [ + // roundtrip + (0.0, 0.0, Status::OK), + (-0.0, -0.0, Status::OK), + (1.0, 1.0, Status::OK), + (-1.0, -1.0, Status::OK), + (<$f>::INFINITY, <$f>::INFINITY, Status::OK), + (<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK), + // with rounding + (0.1, 0.0, Status::INEXACT), + (-0.1, -1.0, Status::INEXACT), + (0.5, 0.0, Status::INEXACT), + (-0.5, -1.0, Status::INEXACT), + (0.9, 0.0, Status::INEXACT), + (-0.9, -1.0, Status::INEXACT), + (1.1, 1.0, Status::INEXACT), + (-1.1, -2.0, Status::INEXACT), + (1.5, 1.0, Status::INEXACT), + (-1.5, -2.0, Status::INEXACT), + (1.9, 1.0, Status::INEXACT), + (-1.9, -2.0, Status::INEXACT), + ] + }; } - /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */ + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { + let FpResult { val, status } = floor_status(x); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); + } + } #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = []; - spec_test::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f32() { - assert_eq!(floor(0.5f32), 0.0); - assert_eq!(floor(1.1f32), 1.0); - assert_eq!(floor(2.9f32), 2.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[test] - fn spec_tests_f32() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -1.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -1.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -2.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -2.0, Status::INEXACT), - ]; - spec_test::(&cases); - } - - #[test] - fn sanity_check_f64() { - assert_eq!(floor(1.1f64), 1.0); - assert_eq!(floor(2.9f64), 2.0); - } - - #[test] - fn spec_tests_f64() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -1.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -1.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -2.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -2.0, Status::INEXACT), - ]; - spec_test::(&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/trunc.rs b/library/compiler-builtins/libm/src/math/generic/trunc.rs index d5b444d15dfc..686436c43b95 100644 --- a/library/compiler-builtins/libm/src/math/generic/trunc.rs +++ b/library/compiler-builtins/libm/src/math/generic/trunc.rs @@ -13,35 +13,29 @@ pub fn trunc_status(x: F) -> FpResult { let mut 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 + // If the exponent is negative, the result will be zero so we mask out everything // except the sign. F::SIGN_MASK } else { - // C3: Otherwise, we mask out the last `e` bits of the significand. + // Otherwise, we mask out the last `e` bits of the significand. !(F::SIG_MASK >> e.unsigned()) }; - // C4: If the to-be-masked-out portion is already zero, we have an exact result + // 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 + // 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 { - Status::OK - } else { - Status::INEXACT - }; xi &= mask; - FpResult::new(F::from_bits(xi), status) + FpResult::new(F::from_bits(xi), Status::INEXACT) } #[cfg(test)] @@ -49,100 +43,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)); - } - - for &(x, res, res_stat) in cases { - let FpResult { val, status } = trunc_status(x); - assert_biteq!(val, res, "{}", Hexf(x)); - assert_eq!(status, res_stat, "{}", Hexf(x)); - } + macro_rules! cases { + ($f:ty) => { + [ + // roundtrip + (0.0, 0.0, Status::OK), + (-0.0, -0.0, Status::OK), + (1.0, 1.0, Status::OK), + (-1.0, -1.0, Status::OK), + (<$f>::INFINITY, <$f>::INFINITY, Status::OK), + (<$f>::NEG_INFINITY, <$f>::NEG_INFINITY, Status::OK), + // with rounding + (0.1, 0.0, Status::INEXACT), + (-0.1, -0.0, Status::INEXACT), + (0.5, 0.0, Status::INEXACT), + (-0.5, -0.0, Status::INEXACT), + (0.9, 0.0, Status::INEXACT), + (-0.9, -0.0, Status::INEXACT), + (1.1, 1.0, Status::INEXACT), + (-1.1, -1.0, Status::INEXACT), + (1.5, 1.0, Status::INEXACT), + (-1.5, -1.0, Status::INEXACT), + (1.9, 1.0, Status::INEXACT), + (-1.9, -1.0, Status::INEXACT), + ] + }; } - /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */ + #[track_caller] + fn check(cases: &[(F, F, Status)]) { + for &(x, exp_res, exp_stat) in cases { + let FpResult { val, status } = trunc_status(x); + assert_biteq!(val, exp_res, "{x:?} {}", Hexf(x)); + assert_eq!( + status, + exp_stat, + "{x:?} {} -> {exp_res:?} {}", + Hexf(x), + Hexf(exp_res) + ); + } + } #[test] #[cfg(f16_enabled)] - fn spec_tests_f16() { - let cases = []; - spec_test::(&cases); + fn check_f16() { + check::(&cases!(f16)); + check::(&[ + (hf16!("0x1p10"), hf16!("0x1p10"), Status::OK), + (hf16!("-0x1p10"), hf16!("-0x1p10"), Status::OK), + ]); } #[test] - fn sanity_check_f32() { - assert_eq!(trunc(0.5f32), 0.0); - assert_eq!(trunc(1.1f32), 1.0); - assert_eq!(trunc(2.9f32), 2.0); + fn check_f32() { + check::(&cases!(f32)); + check::(&[ + (hf32!("0x1p23"), hf32!("0x1p23"), Status::OK), + (hf32!("-0x1p23"), hf32!("-0x1p23"), Status::OK), + ]); } #[test] - fn spec_tests_f32() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&cases); - - assert_biteq!(trunc(1.1f32), 1.0); - assert_biteq!(trunc(1.1f64), 1.0); - - // C1 - assert_biteq!(trunc(hf32!("0x1p23")), hf32!("0x1p23")); - assert_biteq!(trunc(hf64!("0x1p52")), hf64!("0x1p52")); - assert_biteq!(trunc(hf32!("-0x1p23")), hf32!("-0x1p23")); - assert_biteq!(trunc(hf64!("-0x1p52")), hf64!("-0x1p52")); - - // C2 - assert_biteq!(trunc(hf32!("0x1p-1")), 0.0); - assert_biteq!(trunc(hf64!("0x1p-1")), 0.0); - assert_biteq!(trunc(hf32!("-0x1p-1")), -0.0); - assert_biteq!(trunc(hf64!("-0x1p-1")), -0.0); - } - - #[test] - fn sanity_check_f64() { - assert_eq!(trunc(1.1f64), 1.0); - assert_eq!(trunc(2.9f64), 2.0); - } - - #[test] - fn spec_tests_f64() { - let cases = [ - (0.1, 0.0, Status::INEXACT), - (-0.1, -0.0, Status::INEXACT), - (0.9, 0.0, Status::INEXACT), - (-0.9, -0.0, Status::INEXACT), - (1.1, 1.0, Status::INEXACT), - (-1.1, -1.0, Status::INEXACT), - (1.9, 1.0, Status::INEXACT), - (-1.9, -1.0, Status::INEXACT), - ]; - spec_test::(&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), + ]); } } From 8c26adcab0d57791c2af752fd1c0973860bc38f5 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 09:26:48 -0600 Subject: [PATCH 20/50] libm: Add an optimization for `floor` ceil seems to optimize better, but this gets closer to the pre-fix codegen. --- .../libm/src/math/generic/floor.rs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/floor.rs b/library/compiler-builtins/libm/src/math/generic/floor.rs index c763a3cc265e..7045229c0c75 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::SIGN_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)] From dffcc200c07f756612b2ab20ac0acf96f7ca3d19 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 02:23:56 -0600 Subject: [PATCH 21/50] libm: Add an optimization for `trunc` Suggested-by: Juho Kahala <57393910+quaternic@users.noreply.github.com> --- .../libm/src/math/generic/trunc.rs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/generic/trunc.rs b/library/compiler-builtins/libm/src/math/generic/trunc.rs index 686436c43b95..7f18eb42e884 100644 --- a/library/compiler-builtins/libm/src/math/generic/trunc.rs +++ b/library/compiler-builtins/libm/src/math/generic/trunc.rs @@ -10,7 +10,7 @@ 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(); // The represented value has no fractional part, so no truncation is needed @@ -18,24 +18,26 @@ pub fn trunc_status(x: F) -> FpResult { return FpResult::ok(x); } - let mask = if e < 0 { - // 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 { - // 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() }; - // If the to-be-masked-out portion is already zero, we have an exact result - if (xi & !mask) == IntTy::::ZERO { - return FpResult::ok(x); - } + 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 + }; - // Otherwise the result is inexact and we will truncate. Raise `FE_INEXACT`, mask the - // result, and return. - xi &= mask; - FpResult::new(F::from_bits(xi), Status::INEXACT) + // Now zero the bits we need to truncate and return. + FpResult::new(F::from_bits(xi ^ cleared), status) } #[cfg(test)] From 8fd2474e2deb9ec1e44c7f8166c20cb3f129e1f5 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 7 Feb 2026 04:11:18 -0600 Subject: [PATCH 22/50] meta: Upgrade all dependencies to the latest incompatible versions Most of the semver-breaking changes here relate to the 0.10 release of `rand`. Changelogs: * https://github.com/criterion-rs/criterion.rs/releases/tag/criterion-v0.8.0 * https://github.com/gimli-rs/object/blob/master/CHANGELOG.md#0380 * https://github.com/rust-random/rand/blob/master/CHANGELOG.md#0100---2026-02-08 --- library/compiler-builtins/Cargo.toml | 22 +++++++++---------- .../builtins-test/src/lib.rs | 2 +- .../libm-test/src/generate/random.rs | 2 +- .../compiler-builtins/libm-test/tests/u256.rs | 2 +- library/compiler-builtins/libm/Cargo.toml | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/library/compiler-builtins/Cargo.toml b/library/compiler-builtins/Cargo.toml index 785df6a20015..26a056f43496 100644 --- a/library/compiler-builtins/Cargo.toml +++ b/library/compiler-builtins/Cargo.toml @@ -34,14 +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"] } +criterion = { version = "0.8.2", default-features = false, features = ["cargo_bench_support"] } getopts = "0.2.24" -getrandom = "0.3.4" +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 } @@ -49,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/src/lib.rs b/library/compiler-builtins/builtins-test/src/lib.rs index f1673133be27..b9ad649f88dd 100644 --- a/library/compiler-builtins/builtins-test/src/lib.rs +++ b/library/compiler-builtins/builtins-test/src/lib.rs @@ -22,7 +22,7 @@ use compiler_builtins::float::Float; use compiler_builtins::int::{Int, MinInt}; use rand_xoshiro::Xoshiro128StarStar; -use rand_xoshiro::rand_core::{RngCore, SeedableRng}; +use rand_xoshiro::rand_core::{Rng, SeedableRng}; /// Sets the number of fuzz iterations run for most tests. In practice, the vast majority of bugs /// are caught by the edge case testers. Most of the remaining bugs triggered by more complex diff --git a/library/compiler-builtins/libm-test/src/generate/random.rs b/library/compiler-builtins/libm-test/src/generate/random.rs index 4ee88946d8ea..09a3766c6678 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 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/tests/u256.rs b/library/compiler-builtins/libm-test/tests/u256.rs index d1c5cfbcc586..e697945f4797 100644 --- a/library/compiler-builtins/libm-test/tests/u256.rs +++ b/library/compiler-builtins/libm-test/tests/u256.rs @@ -10,7 +10,7 @@ use libm_test::bigint_fuzz_iteration_count; use libm_test::generate::random::SEED; -use rand::{Rng, SeedableRng}; +use rand::{RngExt, SeedableRng}; use rand_chacha::ChaCha8Rng; use rug::Assign; use rug::integer::Order; diff --git a/library/compiler-builtins/libm/Cargo.toml b/library/compiler-builtins/libm/Cargo.toml index 98202d1977dc..28e594dca1f9 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"] From 706a3ab68e65c007ba236a6f39189dd9b218834b Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 13 Feb 2026 07:46:29 -0600 Subject: [PATCH 23/50] meta: Move the Criterion dependency behind a `walltime` feature Currently Criterion is always built with the test crates, but it doesn't really need to be. It wasn't a problem before but did start showing up with the latest version bump since it added a C dependency, and for the Miri CI runs we don't have the C toolchains. To resolve this and speed up compilation time, move criterion behind a new feature `walltime`. I'd rather have named this `runtime` but don't want to confuse my future self with "what runtime?". --- .../.github/workflows/main.yaml | 2 +- .../builtins-test/Cargo.toml | 19 +++++++++++++++---- .../{bench-runtime.sh => bench-walltime.sh} | 2 +- .../compiler-builtins/libm-test/Cargo.toml | 13 +++++++++---- 4 files changed, 26 insertions(+), 10 deletions(-) rename library/compiler-builtins/ci/{bench-runtime.sh => bench-walltime.sh} (77%) diff --git a/library/compiler-builtins/.github/workflows/main.yaml b/library/compiler-builtins/.github/workflows/main.yaml index 1a69f7484d88..261b1619f1c5 100644 --- a/library/compiler-builtins/.github/workflows/main.yaml +++ b/library/compiler-builtins/.github/workflows/main.yaml @@ -287,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() diff --git a/library/compiler-builtins/builtins-test/Cargo.toml b/library/compiler-builtins/builtins-test/Cargo.toml index 9395ab1a985e..b2313bb9d140 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/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 d272cf33463e..0393d02dfc45 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/libm-test/Cargo.toml b/library/compiler-builtins/libm-test/Cargo.toml index 4f65504bd584..eb382ad87f5f 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,6 +19,10 @@ 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"] } @@ -27,7 +30,6 @@ getrandom = { workspace = true, features = ["wasm_js"] } rand = { workspace = true, optional = true } [dev-dependencies] -criterion.workspace = true libtest-mimic.workspace = true [features] @@ -43,8 +45,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 +64,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 From 873e96b6752a323f149495abc2479d3d70df611a Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 13 Feb 2026 08:34:31 -0600 Subject: [PATCH 24/50] ci: Rewrap the command in miri.sh --- library/compiler-builtins/ci/miri.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/compiler-builtins/ci/miri.sh b/library/compiler-builtins/ci/miri.sh index 7b0ea44c690f..aae474d88463 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 From 87166b545e435a1bffac968a023260d3795e532c Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 13 Feb 2026 22:59:28 -0500 Subject: [PATCH 25/50] libm: Reenable should_panic tests on ppc64le The tests pass successfully for me on powerpc64le-unknown-linux-gnu with nightly 1.95. --- library/compiler-builtins/libm/src/math/support/big/tests.rs | 2 -- library/compiler-builtins/libm/src/math/support/hex_float.rs | 2 -- 2 files changed, 4 deletions(-) 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 0c32f445c136..2eafed50a275 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/hex_float.rs b/library/compiler-builtins/libm/src/math/support/hex_float.rs index 4495e3c18801..81f6b86686c4 100644 --- a/library/compiler-builtins/libm/src/math/support/hex_float.rs +++ b/library/compiler-builtins/libm/src/math/support/hex_float.rs @@ -846,8 +846,6 @@ fn test_macros() { } #[cfg(test)] -// FIXME(ppc): something with `should_panic` tests cause a SIGILL with ppc64le -#[cfg(not(all(target_arch = "powerpc64", target_endian = "little")))] mod tests_panicking { extern crate std; use super::*; From 7d9b4120739fa80e1fa04b2448b631148d37810d Mon Sep 17 00:00:00 2001 From: Aelin Date: Sat, 14 Feb 2026 21:57:28 +0100 Subject: [PATCH 26/50] libm: Reenable sincosf tests on ppc64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was missed in 6e91b0346c3d (“Revert "Disable broken powerpc64 test due to https://github.com/rust-lang/rust/issues/88520"”). --- library/compiler-builtins/libm/src/math/sincosf.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/sincosf.rs b/library/compiler-builtins/libm/src/math/sincosf.rs index c4beb5267f28..1d15abe54306 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; From 49e594e11af4ada0d3b44f7414c5f8b515de7474 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 01:18:47 -0600 Subject: [PATCH 27/50] ci: Allow specifying extra extensive tests to run For cases where we would like to run tests that aren't automatically detected, allow the following syntax in PR descriptions: ci: extra-extensive=copysignf,sqrtf16 --- library/compiler-builtins/ci/ci-util.py | 64 +++++++++++++++++++++---- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/library/compiler-builtins/ci/ci-util.py b/library/compiler-builtins/ci/ci-util.py index ef9ce455178e..1fd8e72077ff 100755 --- a/library/compiler-builtins/ci/ci-util.py +++ b/library/compiler-builtins/ci/ci-util.py @@ -11,7 +11,7 @@ import pprint import re import subprocess as sp import sys -from dataclasses import dataclass +from dataclasses import dataclass, field from functools import cache from glob import glob from inspect import cleandoc @@ -19,8 +19,7 @@ from os import getenv from pathlib import Path from typing import TypedDict, Self -USAGE = cleandoc( - """ +USAGE = cleandoc(""" usage: ./ci/ci-util.py [flags] @@ -44,8 +43,7 @@ USAGE = cleandoc( Exit with success if the pull request contains a line starting with `ci: allow-regressions`, indicating that regressions in benchmarks should be accepted. Otherwise, exit 1. - """ -) + """) REPO_ROOT = Path(__file__).parent.parent GIT = ["git", "-C", REPO_ROOT] @@ -84,6 +82,8 @@ class PrCfg: 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) # 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 +101,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) + directives = re.finditer( + r"^\s*ci:\s*(?P[^\s=]*)(?:\s*=\s*(?P.*))?", + 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,10 +120,17 @@ class PrCfg: self.allow_many_extensive = True elif name == self.DIR_TEST_LIBM: self.always_test_libm = True + elif name == self.DIR_EXTRA_EXTENSIVE: + self.extra_extensive = [x.strip() for x in args.split(",")] + args = None else: eprint(f"Found unexpected directive `{name}`") exit(1) + if args is not None: + eprint("Found arguments where not expected") + exit(1) + pprint.pp(self) @@ -276,29 +289,35 @@ class Context: skip_tests = False error_on_many_tests = False + extra_tests = {} pr = PrInfo.from_env() if pr is not None: skip_tests = pr.cfg.skip_extensive error_on_many_tests = not pr.cfg.allow_many_extensive + for fn_name in pr.cfg.extra_extensive: + extra_tests.setdefault(base_name(fn_name)[1], []).append(fn_name) if skip_tests: eprint("Skipping all extensive tests") changed = self.changed_routines() + eprint(f"Changed: {changed}") + matrix = [] total_to_test = 0 # Figure out which extensive tests need to run for ty in TYPES: ty_changed = changed.get(ty, []) - ty_to_test = [] if skip_tests else ty_changed + ty_to_test = [] if skip_tests else ty_changed.copy() + ty_to_test.extend(extra_tests.get(ty, [])) total_to_test += len(ty_to_test) item = { "ty": ty, - "changed": ",".join(ty_changed), - "to_test": ",".join(ty_to_test), + "changed": ",".join(sorted(set(ty_changed))), + "to_test": ",".join(sorted(set(ty_to_test))), } matrix.append(item) @@ -319,6 +338,33 @@ class Context: exit(1) +def base_name(name: str) -> tuple[str, str]: + """Return the basename and type from a full function name. Keep in sync with Rust's + `fn base_name`. + """ + known_mappings = [ + ("erff", ("erf", "f32")), + ("erf", ("erf", "f64")), + ("modff", ("modf", "f32")), + ("modf", ("modf", "f64")), + ("lgammaf_r", ("lgamma_r", "f32")), + ("lgamma_r", ("lgamma_r", "f64")), + ] + + found = next((base for (full, base) in known_mappings if full == name), None) + if found is not None: + return found + + if name.endswith("f"): + return (name.rstrip("f"), "f32") + elif name.endswith("f16"): + return (name.rstrip("f16"), "f16") + elif name.endswith("f128"): + return (name.rstrip("f128"), "f128") + + return (name, "f64") + + def locate_baseline(flags: list[str]) -> None: """Find the most recent baseline from CI, download it if specified. From 9be3b21559a5de22ee3920dc91811d4f1c5ac65c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 02:35:29 -0600 Subject: [PATCH 28/50] meta: Add a root Cargo.lock file In order to improve upon some inconsistencies seen across CI runs and locally, add a Cargo.lock file for the main workspace. --- library/compiler-builtins/.gitignore | 6 +- library/compiler-builtins/Cargo.lock | 1644 ++++++++++++++++++++++++++ 2 files changed, 1649 insertions(+), 1 deletion(-) create mode 100644 library/compiler-builtins/Cargo.lock diff --git a/library/compiler-builtins/.gitignore b/library/compiler-builtins/.gitignore index abe346659d4c..1137a19cf093 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 000000000000..4bbdf5f4ae20 --- /dev/null +++ b/library/compiler-builtins/Cargo.lock @@ -0,0 +1,1644 @@ +# 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", +] + +[[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 = "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" From e12d83967e0537f4066f18a485d61e044f0314b6 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 03:02:14 -0600 Subject: [PATCH 29/50] ci: Resolve an issue calculating workflow variables PRs are now getting the following: Traceback (most recent call last): File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 510, in main() File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 496, in main ctx.emit_workflow_output() File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 294, in emit_workflow_output pr = PrInfo.from_env() ^^^^^^^^^^^^^^^^^ File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 152, in from_env return cls.from_pr(pr_env) ^^^^^^^^^^^^^^^^^^^ File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 174, in from_pr return cls(**json.loads(pr_info), cfg=PrCfg(pr_json["body"])) ^^^^^^^^^^^^^^^^^^^^^^ File "/home/runner/work/compiler-builtins/compiler-builtins/ci/ci-util.py", line 134, in __init__ pprint.pp(self) File "/usr/lib/python3.12/pprint.py", line 66, in pp pprint(object, *args, sort_dicts=sort_dicts, **kwargs) ... AttributeError: 'PrCfg' object has no attribute 'extra_extensive'. Did you mean: 'skip_extensive'? Resolve this by using `__post_init__` rather than `__init__`. Fixes: bba024d20464 ("ci: Allow specifying extra extensive tests to run") --- library/compiler-builtins/ci/ci-util.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/compiler-builtins/ci/ci-util.py b/library/compiler-builtins/ci/ci-util.py index 1fd8e72077ff..392f83c219e7 100755 --- a/library/compiler-builtins/ci/ci-util.py +++ b/library/compiler-builtins/ci/ci-util.py @@ -71,19 +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) + 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 @@ -103,10 +105,10 @@ class PrCfg: DIR_TEST_LIBM: str = "test-libm" DIR_EXTRA_EXTENSIVE: str = "extra-extensive" - def __init__(self, body: str): + def __post_init__(self): directives = re.finditer( r"^\s*ci:\s*(?P[^\s=]*)(?:\s*=\s*(?P.*))?", - body, + self.body, re.MULTILINE, ) for dir in directives: @@ -131,7 +133,7 @@ class PrCfg: eprint("Found arguments where not expected") exit(1) - pprint.pp(self) + eprint(pprint.pformat(self)) @dataclass @@ -171,7 +173,7 @@ class PrInfo: ) pr_json = json.loads(pr_info) eprint("PR info:", json.dumps(pr_json, indent=4)) - return cls(**json.loads(pr_info), cfg=PrCfg(pr_json["body"])) + return cls(**pr_json, cfg=PrCfg(body=pr_json["body"])) class FunctionDef(TypedDict): From 1542d6405c144697cd9d5f50c5db55c0931651af Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 16 Feb 2026 03:40:01 -0600 Subject: [PATCH 30/50] test: Enable the `wasmbind` feature on indicatif for wasm As of indicatif 0.18.4, this option must be enabled in able to build. --- library/compiler-builtins/Cargo.lock | 11 +++++++++++ library/compiler-builtins/libm-test/Cargo.toml | 1 + 2 files changed, 12 insertions(+) diff --git a/library/compiler-builtins/Cargo.lock b/library/compiler-builtins/Cargo.lock index 4bbdf5f4ae20..7a3e9a38430b 100644 --- a/library/compiler-builtins/Cargo.lock +++ b/library/compiler-builtins/Cargo.lock @@ -590,6 +590,7 @@ dependencies = [ "console", "portable-atomic", "unit-prefix", + "web-time", ] [[package]] @@ -1409,6 +1410,16 @@ dependencies = [ "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" diff --git a/library/compiler-builtins/libm-test/Cargo.toml b/library/compiler-builtins/libm-test/Cargo.toml index eb382ad87f5f..8a8c2b0a2ce0 100644 --- a/library/compiler-builtins/libm-test/Cargo.toml +++ b/library/compiler-builtins/libm-test/Cargo.toml @@ -25,6 +25,7 @@ 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 } From 04334f05157ce6f0d7898de357d9ddd37769f6ef Mon Sep 17 00:00:00 2001 From: Juho Kahala <57393910+quaternic@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:36:15 +0200 Subject: [PATCH 31/50] fix testing at lgammaf overflow threshold The current implementation of lgammaf evaluates to f32::MAX at the first input that would overflow if correctly rounded. This is still well within allowed error, so special case it. --- library/compiler-builtins/libm-test/src/precision.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/compiler-builtins/libm-test/src/precision.rs b/library/compiler-builtins/libm-test/src/precision.rs index 5d52da168fe7..8c01c9e3d491 100644 --- a/library/compiler-builtins/libm-test/src/precision.rs +++ b/library/compiler-builtins/libm-test/src/precision.rs @@ -222,6 +222,15 @@ fn check_float(input: (f32,), actual: F, expected: F, ctx: &CheckCtx) return XFAIL_NOCHECK; } + // the testing infrastructure doesn't account for allowed ulp in the case of overflow + if matches!(ctx.base_name, BaseName::Lgamma | BaseName::LgammaR) + && input.0 == 4.0850034e36 + && expected.is_infinite() + && actual == F::MAX + { + return XFAIL_NOCHECK; + } + // FIXME(correctness): lgammaf has high relative inaccuracy near its zeroes if matches!(ctx.base_name, BaseName::Lgamma | BaseName::LgammaR) && input.0 > -13.0625 From 84cfd9a557167fe453f7f6474dbea02b76f46b1b Mon Sep 17 00:00:00 2001 From: Hans Wennborg Date: Tue, 3 Feb 2026 17:02:22 +0100 Subject: [PATCH 32/50] Check for symbols with default visibility in symbol-check to ensure that compiler-builtins doesn't expose any symbols with default visibility. --- .../crates/symbol-check/src/main.rs | 43 +++++++++++++++++-- .../crates/symbol-check/tests/all.rs | 26 +++++++++-- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index e15522d223d8..135019e5f729 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -58,6 +58,7 @@ fn main() { "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)); @@ -74,6 +75,7 @@ fn main() { } 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!( @@ -85,18 +87,18 @@ fn main() { 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_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_paths(&free_args, no_os_target, check_visibility); } else { print_usage_and_exit(1); } } -fn check_paths>(paths: &[P], no_os_target: bool) { +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()); @@ -105,6 +107,9 @@ fn check_paths>(paths: &[P], no_os_target: bool) { verify_no_duplicates(&archive); verify_core_symbols(&archive); verify_no_exec_stack(&archive, no_os_target); + if check_visibility { + verify_hidden_visibility(&archive); + } } } @@ -329,6 +334,38 @@ 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, diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index 6dc34c3e3dba..cfc8641aca82 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -75,6 +75,20 @@ fn test_core_symbols() { .stderr_contains("from_utf8"); } +#[test] +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"); + 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::*; @@ -95,7 +109,7 @@ fn test_missing_gnu_stack_section() { 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).assert(); + 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 @@ -127,7 +141,7 @@ fn test_exe_gnu_stack_section() { .compile_intermediates(); let [obj] = objs.as_slice() else { panic!() }; - let assert = t.symcheck_exe().arg(obj).assert(); + 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. @@ -179,7 +193,11 @@ fn test_good_lib() { 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).assert(); + let assert = t + .symcheck_exe() + .arg(&lib_out) + .arg("--no-visibility") + .assert(); assert.success(); } @@ -199,7 +217,7 @@ fn test_good_bin() { t.set_bin_out_path(&mut cmd, &out); run(cmd.arg(input_dir().join("good_bin.c"))); - let assert = t.symcheck_exe().arg(&out).assert(); + let assert = t.symcheck_exe().arg(&out).arg("--no-visibility").assert(); assert.success(); } From d31b87f20b28d3cc2e4f26bc3de465aa209de670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Freitas?= Date: Mon, 12 Jan 2026 01:30:00 -0300 Subject: [PATCH 33/50] Add Apple AArch64 support for outline atomics Add support for ELF and Mach-O AArch64 relocation specifiers for symbol access, enabling outline-atomics to work on Apple targets. Remove the Linux-only OS gate in tests. Co-authored-by: Trevor Gross --- .../builtins-test/tests/lse.rs | 2 +- .../src/aarch64_outline_atomics.rs | 69 +++++++++++++++++-- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index 56891be8a8ac..854ea021b4b1 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -1,6 +1,6 @@ #![feature(decl_macro)] // so we can use pub(super) #![feature(macro_metavar_expr_concat)] -#![cfg(all(target_arch = "aarch64", target_os = "linux"))] +#![cfg(target_arch = "aarch64")] /// Translate a byte size to a Rust type. macro int_ty { 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 df0cf7650222..3245523280d7 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs @@ -135,18 +135,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:" ) }; } From 010e20923b4b660d567b6d260fcb408a337e7cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Freitas?= Date: Thu, 22 Jan 2026 00:39:03 -0300 Subject: [PATCH 34/50] Change atomic function signatures to use unsigned integer types Replace signed integer types (i8, i16, i32, i64, i128) with their unsigned equivalents (u8, u16, u32, u64, u128) in function declarations and the int_ty! macro, avoiding the need for sign extension in emitted assembly. --- .../builtins-test/tests/lse.rs | 10 +++++----- .../src/aarch64_outline_atomics.rs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index 854ea021b4b1..b727a223e5c9 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -4,11 +4,11 @@ /// 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 { 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 3245523280d7..54232fe5c62e 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs @@ -37,11 +37,11 @@ pub extern "C" fn __rust_enable_lse() { /// 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. @@ -258,15 +258,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]), @@ -446,7 +446,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); From 008f3134668f667a2430008131452d7cbacf2354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Freitas?= Date: Mon, 12 Jan 2026 02:30:22 -0300 Subject: [PATCH 35/50] Add comprehensive tests for AArch64 atomics with and without LSE Enable aarch64 outline-atomics in tests via the mangled-names feature gate, and add comprehensive compare-and-swap tests that run both with LSE enabled and disabled. Improve assertion messages to better describe expected behavior. Co-authored-by: Trevor Gross --- .../builtins-test/tests/lse.rs | 102 +++++++++++++----- .../src/aarch64_outline_atomics.rs | 13 +++ .../compiler-builtins/src/lib.rs | 7 +- 3 files changed, 93 insertions(+), 29 deletions(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index b727a223e5c9..03fe9467a2fe 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -2,6 +2,45 @@ #![feature(macro_metavar_expr_concat)] #![cfg(target_arch = "aarch64")] +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) => { u8 }, @@ -15,16 +54,17 @@ 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 +75,17 @@ fn $name() { ); target = expected; + let ret: super::int_ty!($bytes) = unsafe { + compiler_builtins::aarch64_outline_atomics::$name::$name( + expected, + new, + &mut target, + ) + }; assert_eq!( - unsafe { - compiler_builtins::aarch64_outline_atomics::$name::$name( - expected, - new, - &mut target, - ) - }, - expected + ret, + expected, + "the new return value should always be the previous value (i.e. the first parameter passed to the function)", ); assert_eq!(target, new, "should have updated target"); }); @@ -59,16 +101,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 +127,7 @@ mod $mod { ($_ordering:ident, $bytes:tt, $name:ident) => { #[test] fn $name() { - builtins_test::fuzz_2(10000, |old, val| { + crate::run_fuzz_tests_with_lse_variants(10000, |old, val| { let mut target = old; let op: fn(super::int_ty!($bytes), super::int_ty!($bytes)) -> _ = $($op)*; let expected = op(old, val); @@ -98,7 +145,6 @@ fn $name() { test_op!(clr, |left, right| left & !right); test_op!(xor, std::ops::BitXor::bitxor); test_op!(or, std::ops::BitOr::bitor); -use compiler_builtins::{foreach_bytes, foreach_ordering}; compiler_builtins::foreach_cas!(cas::test); compiler_builtins::foreach_cas16!(test_cas16); compiler_builtins::foreach_swp!(swap::test); 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 54232fe5c62e..100b67150772 100644 --- a/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs +++ b/library/compiler-builtins/compiler-builtins/src/aarch64_outline_atomics.rs @@ -34,6 +34,19 @@ pub extern "C" fn __rust_enable_lse() { } } +/// Function to enable/disable LSE. To be used only for testing purposes. +#[cfg(feature = "mangled-names")] +pub unsafe fn set_have_lse_atomics(has_lse: bool) { + let lse_flag = if has_lse { 1 } else { 0 }; + HAVE_LSE_ATOMICS.store(lse_flag, Ordering::Relaxed); +} + +/// Function to obtain whether LSE is enabled or not. To be used only for testing purposes. +#[cfg(feature = "mangled-names")] +pub fn get_have_lse_atomics() -> bool { + HAVE_LSE_ATOMICS.load(Ordering::Relaxed) != 0 +} + /// Translate a byte size to a Rust type. #[rustfmt::skip] macro_rules! int_ty { diff --git a/library/compiler-builtins/compiler-builtins/src/lib.rs b/library/compiler-builtins/compiler-builtins/src/lib.rs index 80395a4738eb..a027cd978af2 100644 --- a/library/compiler-builtins/compiler-builtins/src/lib.rs +++ b/library/compiler-builtins/compiler-builtins/src/lib.rs @@ -57,7 +57,12 @@ #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))] pub mod aarch64; -#[cfg(all(target_arch = "aarch64", target_feature = "outline-atomics"))] +// Note that we enable the module on "mangled-names" because that is the default feature +// in the builtins-test tests. So this is a way of enabling the module during testing. +#[cfg(all( + target_arch = "aarch64", + any(target_feature = "outline-atomics", feature = "mangled-names") +))] pub mod aarch64_outline_atomics; #[cfg(target_arch = "avr")] From 00ea98816d1ccd5c7c5d4e6aee287e656e2d1d60 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 20 Feb 2026 16:43:15 -0500 Subject: [PATCH 36/50] test: Gate LSE tests by mangled-names Resolve a CI issue when running tests with `--no-default-features`. --- library/compiler-builtins/builtins-test/tests/lse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/compiler-builtins/builtins-test/tests/lse.rs b/library/compiler-builtins/builtins-test/tests/lse.rs index 03fe9467a2fe..c45fc161b6c4 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -1,6 +1,6 @@ #![feature(decl_macro)] // so we can use pub(super) #![feature(macro_metavar_expr_concat)] -#![cfg(target_arch = "aarch64")] +#![cfg(all(target_arch = "aarch64", feature = "mangled-names"))] use std::sync::Mutex; From 7ba44756aa8ef5bfca675e8424e6043ce6d609b9 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:10:16 -0600 Subject: [PATCH 37/50] c-b: Clean up unused imports These are remnants of historical chkstk implementations that are no longer relevant, so clean up the imports here. --- library/compiler-builtins/compiler-builtins/src/aarch64.rs | 4 ---- library/compiler-builtins/compiler-builtins/src/x86.rs | 4 ---- library/compiler-builtins/compiler-builtins/src/x86_64.rs | 4 ---- 3 files changed, 12 deletions(-) diff --git a/library/compiler-builtins/compiler-builtins/src/aarch64.rs b/library/compiler-builtins/compiler-builtins/src/aarch64.rs index 1b230a214eef..2c12cb7f3095 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/x86.rs b/library/compiler-builtins/compiler-builtins/src/x86.rs index 1a3c41860945..45f4ba207e6e 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 99a527ee9ac5..a3a5c8f7e10f 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 From c5e7c934061869ecc494bc8a3557a0f3e34558c2 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:53:06 -0600 Subject: [PATCH 38/50] c-b: Remove `#![feature(naked_functions)]` This feature has been stable for a while. --- library/compiler-builtins/compiler-builtins/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/compiler-builtins/compiler-builtins/src/lib.rs b/library/compiler-builtins/compiler-builtins/src/lib.rs index a027cd978af2..07960222f20f 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)] From 2b76199bfb822e356d9faff67e43f5769ef31beb Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:57:14 -0600 Subject: [PATCH 39/50] c-b: Remove unused features in tests --- library/compiler-builtins/builtins-test-intrinsics/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/compiler-builtins/builtins-test-intrinsics/src/main.rs b/library/compiler-builtins/builtins-test-intrinsics/src/main.rs index b9d19ea77256..848f00de44d1 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)] #![deny(dead_code)] -#![feature(allocator_api)] #![feature(f128)] #![feature(f16)] #![feature(lang_items)] From 8b9bbbccef2da23cb195a3d2d783f886002611ac Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 04:37:49 -0600 Subject: [PATCH 40/50] c-b: Replace `core::intrinsics::{cold_path, likely}` Replace the use of intrinsics with `core::hint::cold_path` which has been unstably available for a while, and is on track to become stable in the next release. --- .../compiler-builtins/src/mem/impls.rs | 13 +++++++++---- .../compiler-builtins/libm/src/math/support/mod.rs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/library/compiler-builtins/compiler-builtins/src/mem/impls.rs b/library/compiler-builtins/compiler-builtins/src/mem/impls.rs index da16dee25ce5..9681f5d6dac6 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 @@ unsafe fn copy_forward_misaligned_words(dest: *mut u8, src: *const u8, n: usize) let n_words = n & !WORD_MASK; let src_misalignment = src as usize & WORD_MASK; - if likely(src_misalignment == 0) { + if src_misalignment == 0 { copy_forward_aligned_words(dest, src, n_words); } else { + cold_path(); copy_forward_misaligned_words(dest, src, n_words); } dest = dest.wrapping_add(n_words); @@ -327,9 +329,10 @@ unsafe fn copy_backward_misaligned_words(dest: *mut u8, src: *const u8, n: usize let n_words = n & !WORD_MASK; let src_misalignment = src as usize & WORD_MASK; - if likely(src_misalignment == 0) { + if src_misalignment == 0 { copy_backward_aligned_words(dest, src, n_words); } else { + cold_path(); copy_backward_misaligned_words(dest, src, n_words); } dest = dest.wrapping_sub(n_words); @@ -368,7 +371,7 @@ pub unsafe fn set_bytes_words(s: *mut u8, c: u8, n: usize) { } } - if likely(n >= WORD_COPY_THRESHOLD) { + if n >= WORD_COPY_THRESHOLD { // Align s // Because of n >= 2 * WORD_SIZE, dst_misalignment < n let misalignment = (s as usize).wrapping_neg() & WORD_MASK; @@ -380,6 +383,8 @@ pub unsafe fn set_bytes_words(s: *mut u8, c: u8, n: usize) { set_bytes_words(s, c, n_words); s = s.wrapping_add(n_words); n -= n_words; + } else { + cold_path(); } set_bytes_bytes(s, c, n); } diff --git a/library/compiler-builtins/libm/src/math/support/mod.rs b/library/compiler-builtins/libm/src/math/support/mod.rs index 15ab010dc8d5..128ba36e7f01 100644 --- a/library/compiler-builtins/libm/src/math/support/mod.rs +++ b/library/compiler-builtins/libm/src/math/support/mod.rs @@ -35,5 +35,5 @@ /// 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(); } From b4ec29c9939909ae4ed0157ab8bfa2eb9f099b7a Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Feb 2026 05:10:16 -0600 Subject: [PATCH 41/50] libm: Replace `div!` with `unchecked_div_*` `div!` looks innocuous but is actually unsafe. Replace it with function calls that require us to actually use `unsafe`, and add the precondition notes. These can likely be removed completely in the near future. There is one case in `rem_pio2_large` and one in `exp2` where preconditions aren't as easy to define, but fortunately it seems like we are able to just use regular division there without affecting codegen. --- .../compiler-builtins/libm/src/math/exp2.rs | 2 +- .../libm/src/math/lgamma_r.rs | 4 ++- .../libm/src/math/lgammaf_r.rs | 4 ++- .../compiler-builtins/libm/src/math/mod.rs | 18 ---------- .../libm/src/math/rem_pio2_large.rs | 2 +- .../libm/src/math/support/mod.rs | 33 +++++++++++++++++++ .../compiler-builtins/libm/src/math/tgamma.rs | 4 ++- 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/exp2.rs b/library/compiler-builtins/libm/src/math/exp2.rs index 08b71587f6de..d4c9e9665200 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/lgamma_r.rs b/library/compiler-builtins/libm/src/math/lgamma_r.rs index 38eb270f6839..f3c6fef1464d 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 a0b6a678a670..5c087f14d7df 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 b34ab8465349..4bee4478164a 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")] 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 bb2c532916b2..5065ca496b22 100644 --- a/library/compiler-builtins/libm/src/math/rem_pio2_large.rs +++ b/library/compiler-builtins/libm/src/math/rem_pio2_large.rs @@ -255,7 +255,7 @@ extern "C" fn floor(x: f64) -> f64 { /* determine jx,jv,q0, note that 3>q0 */ let jx = nx - 1; - let mut jv = div!(e0 - 3, 24); + let mut jv = (e0 - 3) / 24; if jv < 0 { jv = 0; } diff --git a/library/compiler-builtins/libm/src/math/support/mod.rs b/library/compiler-builtins/libm/src/math/support/mod.rs index 128ba36e7f01..9dc872cdc150 100644 --- a/library/compiler-builtins/libm/src/math/support/mod.rs +++ b/library/compiler-builtins/libm/src/math/support/mod.rs @@ -37,3 +37,36 @@ pub fn cold_path() { #[cfg(intrinsics_enabled)] 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 41415d9d1258..c526842470c8 100644 --- a/library/compiler-builtins/libm/src/math/tgamma.rs +++ b/library/compiler-builtins/libm/src/math/tgamma.rs @@ -23,6 +23,7 @@ most ideas and constants are from boost and python */ use super::{exp, floor, k_cos, k_sin, pow}; +use crate::support::unchecked_div_isize; const PI: f64 = 3.141592653589793238462643383279502884; @@ -37,7 +38,8 @@ fn sinpi(mut x: f64) -> f64 { /* reduce x into [-.25,.25] */ n = (4.0 * x) as isize; - n = div!(n + 1, 2); + // SAFETY: nonzero divisor, nonnegative dividend (`n < 8`). + n = unsafe { unchecked_div_isize(n + 1, 2) }; x -= (n as f64) * 0.5; x *= PI; From 2891905c7d1ae5a40fc3798ab25fab2348734daf Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 19:15:05 -0500 Subject: [PATCH 42/50] meta: Commit changes applied by recent versions of `rustfmt` --- library/compiler-builtins/libm-test/src/precision.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/compiler-builtins/libm-test/src/precision.rs b/library/compiler-builtins/libm-test/src/precision.rs index 8c01c9e3d491..e99424414274 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}; From 44ee391300a7cc4623410145e4aca511cd2bd00e Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 19:17:32 -0500 Subject: [PATCH 43/50] meta: Allow a clippy lint that appears in recent versions We are now getting this warning: warning: the variable `j` is used as a loop counter --> libm/src/math/rem_pio2_large.rs:268:5 | 268 | for i in 0..=m { | ^^^^^^^^^^^^^^ help: consider using: `for (j, i) in ((jv as i32) - (jx as i32)..).zip((0..=m))` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop = note: `#[warn(clippy::explicit_counter_loop)]` on by default warning: `libm` (lib) generated 1 warning Where the suggestion is definitely not an improvement. --- library/compiler-builtins/libm/src/math/rem_pio2_large.rs | 2 ++ 1 file changed, 2 insertions(+) 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 5065ca496b22..841a51b84c27 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 */ /* * ==================================================== From 6c28ca91011e8775f859ac87b678d4a39b7e330f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 23:54:40 -0500 Subject: [PATCH 44/50] meta: Allow or fix `unused_features` These warnings are showing up on the most recent nightly: error: feature `f128` is declared but not used --> builtins-test/tests/addsub.rs:3:35 | 3 | #![cfg_attr(f128_enabled, feature(f128))] | ^^^^ It isn't always easy to be more exact with the features we enable, so mostly ignore this. --- library/compiler-builtins/builtins-test-intrinsics/src/main.rs | 2 +- library/compiler-builtins/builtins-test/tests/addsub.rs | 2 +- library/compiler-builtins/builtins-test/tests/conv.rs | 3 ++- library/compiler-builtins/builtins-test/tests/div_rem.rs | 2 +- library/compiler-builtins/builtins-test/tests/float_pow.rs | 2 +- library/compiler-builtins/builtins-test/tests/lse.rs | 1 + library/compiler-builtins/builtins-test/tests/mul.rs | 2 +- library/compiler-builtins/crates/libm-macros/tests/basic.rs | 1 + 8 files changed, 9 insertions(+), 6 deletions(-) diff --git a/library/compiler-builtins/builtins-test-intrinsics/src/main.rs b/library/compiler-builtins/builtins-test-intrinsics/src/main.rs index 848f00de44d1..a3df2c98376c 100644 --- a/library/compiler-builtins/builtins-test-intrinsics/src/main.rs +++ b/library/compiler-builtins/builtins-test-intrinsics/src/main.rs @@ -3,7 +3,7 @@ // 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(internal_features)] +#![allow(internal_features, unused_features)] #![deny(dead_code)] #![feature(f128)] #![feature(f16)] diff --git a/library/compiler-builtins/builtins-test/tests/addsub.rs b/library/compiler-builtins/builtins-test/tests/addsub.rs index f3334bd0e2d3..410967967d2a 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 9b04295d2efe..0fd15ad3ee66 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 caee4166c995..4ff86385a963 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 a17dff27c105..3cea83a255c8 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 c45fc161b6c4..d408fe193bcf 100644 --- a/library/compiler-builtins/builtins-test/tests/lse.rs +++ b/library/compiler-builtins/builtins-test/tests/lse.rs @@ -1,3 +1,4 @@ +#![allow(unused_features)] #![feature(decl_macro)] // so we can use pub(super) #![feature(macro_metavar_expr_concat)] #![cfg(all(target_arch = "aarch64", feature = "mangled-names"))] diff --git a/library/compiler-builtins/builtins-test/tests/mul.rs b/library/compiler-builtins/builtins-test/tests/mul.rs index bbf1157db42f..30516ebaf786 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/crates/libm-macros/tests/basic.rs b/library/compiler-builtins/crates/libm-macros/tests/basic.rs index b4276262229f..1876db8f5869 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. From c1d0e35db07f961803781cd3e7e49b452056a116 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 6 Mar 2026 01:47:22 -0800 Subject: [PATCH 45/50] c-b: fix path to CONTRIBUTING.md in README.md --- library/compiler-builtins/compiler-builtins/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/compiler-builtins/compiler-builtins/README.md b/library/compiler-builtins/compiler-builtins/README.md index a12bd2ee7349..63cbf33d2aea 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 From aeab5b5cab1d2dd03612f88b734d35ac65d22760 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 6 Mar 2026 04:23:11 -0500 Subject: [PATCH 46/50] c-b: Export `fminimum_num`, `fmaximum_num` and variants Since we are starting to use the `fmaximum_num` intrinsics, LLVM may emit these. --- .../compiler-builtins/compiler-builtins/src/math/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/compiler-builtins/compiler-builtins/src/math/mod.rs b/library/compiler-builtins/compiler-builtins/src/math/mod.rs index 62d729674105..61dfad213cbe 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; From 67633abc1731f64fa6e08a804c085b2b0f89a02c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 19:07:38 -0500 Subject: [PATCH 47/50] support: Add `Hexi` for printing integers in hex Add a wrapper similar to `Hexf` to print integers with proper padding. --- .../libm/src/math/support/hex_float.rs | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) 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 81f6b86686c4..fd070e6bf4da 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); @@ -456,6 +456,53 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } } + + 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"))] From ccd10428843e84e20fb51c2c1a08f107836a853e Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 19:09:37 -0500 Subject: [PATCH 48/50] support: Add different NaN types to the `Float` trait Make `Float::NAN` a qNaN, add sNaNs, and add operations to check whether a NaN is quiet or signaling. The MIPS values are checked against https://gcc.godbolt.org/z/1bhsd5ana. --- .../libm-test/src/f8_impl.rs | 9 +- .../libm/src/math/support/float_traits.rs | 126 +++++++++++++++++- 2 files changed, 127 insertions(+), 8 deletions(-) diff --git a/library/compiler-builtins/libm-test/src/f8_impl.rs b/library/compiler-builtins/libm-test/src/f8_impl.rs index 905c7d7fde92..9f19f518e2a3 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/src/math/support/float_traits.rs b/library/compiler-builtins/libm/src/math/support/float_traits.rs index 9f2ce532ed17..944546601c9c 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 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 @@ fn eq_repr(self, rhs: Self) -> bool { /// Returns true if the value is NaN. fn is_nan(self) -> bool; + fn is_qnan(self) -> bool { + if !self.is_nan() { + return false; + } + + let top = self.to_bits() & Self::SIG_TOP_BIT; + + if MIPS_NAN { + top == Self::Int::ZERO + } else { + top != Self::Int::ZERO + } + } + + fn is_snan(self) -> bool { + self.is_nan() && !self.is_qnan() + } + /// Returns true if the value is +inf or -inf. fn is_infinite(self) -> bool; @@ -223,13 +256,28 @@ impl Float for $ty { const NEG_ONE: Self = -1.0; const INFINITY: Self = Self::INFINITY; const NEG_INFINITY: Self = Self::NEG_INFINITY; - const NAN: Self = Self::NAN; - // NAN isn't guaranteed to be positive but it usually is. We only use this for - // tests. - const NEG_NAN: Self = $from_bits($to_bits(Self::NAN) | Self::SIGN_MASK); const MAX: Self = -Self::MIN; // Sign bit set, saturated mantissa, saturated exponent with last bit zeroed const MIN: Self = $from_bits(Self::Int::MAX & !(1 << Self::SIG_BITS)); + + // The default NaN seems to set one of the top two significand bits on most + // platforms (sNaN vs. qNaN). For mips, the significand is all 1s (with the + // exception of the signaling bit). + const NAN: Self = $from_bits(if MIPS_NAN { + Self::EXP_MASK | (Self::SIG_TOP_BIT - 1) + } else { + Self::EXP_MASK | Self::SIG_TOP_BIT + }); + const SNAN: Self = $from_bits(if MIPS_NAN { + Self::EXP_MASK | Self::SIG_MASK + } else { + Self::EXP_MASK | (Self::SIG_TOP_BIT >> 1) + }); + // NAN isn't guaranteed to be positive but it usually is. We only use these for + // tests. + const NEG_NAN: Self = $from_bits($to_bits(Self::NAN) | Self::SIGN_MASK); + const NEG_SNAN: Self = $from_bits($to_bits(Self::SNAN) | Self::SIGN_MASK); + const EPSILON: Self = <$ty>::EPSILON; // Exponent is a 1 in the LSB @@ -246,6 +294,7 @@ impl Float for $ty { const SIG_MASK: Self::Int = (1 << Self::SIG_BITS) - 1; const EXP_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIG_MASK); const IMPLICIT_BIT: Self::Int = 1 << Self::SIG_BITS; + const SIG_TOP_BIT: Self::Int = Self::IMPLICIT_BIT >> 1; fn to_bits(self) -> Self::Int { self.to_bits() @@ -450,6 +499,17 @@ fn check_f16() { assert_eq!(f16::EXP_MAX, 15); assert_eq!(f16::EXP_MIN, -14); assert_eq!(f16::EXP_MIN_SUBNORM, -24); + assert_biteq!(f16::NAN, ::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); @@ -476,6 +536,21 @@ fn check_f32() { assert_eq!(f32::EXP_MAX, 127); assert_eq!(f32::EXP_MIN, -126); assert_eq!(f32::EXP_MIN_SUBNORM, -149); + assert_biteq!(f32::NAN, ::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); @@ -506,6 +581,21 @@ fn check_f64() { assert_eq!(f64::EXP_MAX, 1023); assert_eq!(f64::EXP_MIN, -1022); assert_eq!(f64::EXP_MIN_SUBNORM, -1074); + assert_biteq!(f64::NAN, ::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); @@ -537,6 +627,30 @@ fn check_f128() { assert_eq!(f128::EXP_MAX, 16383); assert_eq!(f128::EXP_MIN, -16382); assert_eq!(f128::EXP_MIN_SUBNORM, -16494); + assert_biteq!(f128::NAN, ::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); From 3a352497c3ac93654abaa8a376fc14ac448bbfa2 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 19:05:55 -0500 Subject: [PATCH 49/50] support: Print sNaN or qNaN for hex floats Give more descriptive output in tests since we sometimes need to treat these differently. We still don't parse `sNaN`/`qNaN` for now, though we could in the future. --- .../libm/src/math/support/hex_float.rs | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) 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 fd070e6bf4da..5be0d3159de3 100644 --- a/library/compiler-builtins/libm/src/math/support/hex_float.rs +++ b/library/compiler-builtins/libm/src/math/support/hex_float.rs @@ -340,8 +340,10 @@ pub(super) fn fmt_any_hex(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::R write!(f, "-")?; } - if x.is_nan() { - return write!(f, "NaN"); + if x.is_snan() { + return write!(f, "sNaN"); + } else if x.is_nan() { + return write!(f, "qNaN"); } else if x.is_infinite() { return write!(f, "inf"); } else if *x == F::ZERO { @@ -511,6 +513,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(test)] mod parse_tests { extern crate std; + use std::string::String; use std::{format, println}; use super::*; @@ -559,6 +562,16 @@ fn rounding_properties(s: &str) -> Result<(), HexFloatParseError> { } Ok(()) } + + #[cfg_attr(not(f16_enabled), expect(unused))] + pub fn canonicalize_snan_str(s: String) -> String { + if s.contains("sNaN") || s.contains("qNaN") { + s.replace("sNaN", "NaN").replace("qNaN", "NaN") + } else { + s + } + } + #[test] #[cfg(f16_enabled)] fn test_rounding() { @@ -566,7 +579,11 @@ fn test_rounding() { for i in -n..n { let u = i.rotate_right(11) as u32; let s = format!("{}", Hexf(f32::from_bits(u))); - assert!(rounding_properties(&s).is_ok()); + let s = canonicalize_snan_str(s); + match rounding_properties(&s) { + Ok(()) => (), + Err(e) => panic!("failed rounding properties for `{s}`: {e:?}"), + } } } @@ -1099,8 +1116,11 @@ fn test_f16() { use std::format; // Exhaustively check that `f16` roundtrips. for x in 0..=u16::MAX { + use super::parse_tests::canonicalize_snan_str; + let f = f16::from_bits(x); let s = format!("{}", Hexf(f)); + let s = canonicalize_snan_str(s); let from_s = hf16(&s); if f.is_nan() && from_s.is_nan() { @@ -1119,6 +1139,8 @@ fn test_f16() { #[cfg(f16_enabled)] fn test_f16_to_f32() { use std::format; + + use super::parse_tests::canonicalize_snan_str; // Exhaustively check that these are equivalent for all `f16`: // - `f16 -> f32` // - `f16 -> str -> f32` @@ -1127,8 +1149,10 @@ fn test_f16_to_f32() { for x in 0..=u16::MAX { let f16 = f16::from_bits(x); let s16 = format!("{}", Hexf(f16)); + let s16 = canonicalize_snan_str(s16); let f32 = f16 as f32; let s32 = format!("{}", Hexf(f32)); + let s32 = canonicalize_snan_str(s32); let a = hf32(&s16); let b = hf32(&s32); @@ -1169,8 +1193,17 @@ fn spot_checks() { assert_eq!(Hexf(f32::NEG_ZERO).to_string(), "-0x0p+0"); assert_eq!(Hexf(f64::NEG_ZERO).to_string(), "-0x0p+0"); - assert_eq!(Hexf(f32::NAN).to_string(), "NaN"); - assert_eq!(Hexf(f64::NAN).to_string(), "NaN"); + assert_eq!(Hexf(f32::NAN).to_string(), "qNaN"); + assert_eq!(Hexf(f64::NAN).to_string(), "qNaN"); + assert_eq!(Hexf(f32::NEG_NAN).to_string(), "-qNaN"); + assert_eq!(Hexf(f64::NEG_NAN).to_string(), "-qNaN"); + if !cfg!(x86_no_sse) { + // FIXME(rust-lang/rust#115567): calls quiet the sNaN + assert_eq!(Hexf(f32::SNAN).to_string(), "sNaN"); + assert_eq!(Hexf(f64::SNAN).to_string(), "sNaN"); + assert_eq!(Hexf(f32::NEG_SNAN).to_string(), "-sNaN"); + assert_eq!(Hexf(f64::NEG_SNAN).to_string(), "-sNaN"); + } assert_eq!(Hexf(f32::INFINITY).to_string(), "inf"); assert_eq!(Hexf(f64::INFINITY).to_string(), "inf"); @@ -1184,7 +1217,9 @@ fn spot_checks() { assert_eq!(Hexf(f16::MIN).to_string(), "-0x1.ffcp+15"); assert_eq!(Hexf(f16::ZERO).to_string(), "0x0p+0"); assert_eq!(Hexf(f16::NEG_ZERO).to_string(), "-0x0p+0"); - assert_eq!(Hexf(f16::NAN).to_string(), "NaN"); + assert_eq!(Hexf(f16::NAN).to_string(), "qNaN"); + assert_eq!(Hexf(f16::SNAN).to_string(), "sNaN"); + assert_eq!(Hexf(f16::NEG_NAN).to_string(), "-qNaN"); assert_eq!(Hexf(f16::INFINITY).to_string(), "inf"); assert_eq!(Hexf(f16::NEG_INFINITY).to_string(), "-inf"); } @@ -1201,7 +1236,9 @@ fn spot_checks() { ); assert_eq!(Hexf(f128::ZERO).to_string(), "0x0p+0"); assert_eq!(Hexf(f128::NEG_ZERO).to_string(), "-0x0p+0"); - assert_eq!(Hexf(f128::NAN).to_string(), "NaN"); + assert_eq!(Hexf(f128::NAN).to_string(), "qNaN"); + assert_eq!(Hexf(f128::SNAN).to_string(), "sNaN"); + assert_eq!(Hexf(f128::NEG_NAN).to_string(), "-qNaN"); assert_eq!(Hexf(f128::INFINITY).to_string(), "inf"); assert_eq!(Hexf(f128::NEG_INFINITY).to_string(), "-inf"); } From 7a698be60580b1e9d68620e4ca3a8517eed654f5 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Mar 2026 19:11:20 -0500 Subject: [PATCH 50/50] min,max: Add tests for signaling NaNs and update documentation We do handle signaling NaNs properly, with the exception of raising exceptions as IEEE 754 requires. Add tests to this effect for `fmin`, `fminimum`, `fminimum_num`, and the max variants. --- .../libm/src/math/fmin_fmax.rs | 109 ++++++++++++-- .../libm/src/math/fminimum_fmaximum.rs | 137 +++++++++++++----- .../libm/src/math/fminimum_fmaximum_num.rs | 115 +++++++++++++-- .../libm/src/math/generic/fmax.rs | 10 +- .../libm/src/math/generic/fmaximum.rs | 3 +- .../libm/src/math/generic/fmaximum_num.rs | 3 +- .../libm/src/math/generic/fmin.rs | 10 +- .../libm/src/math/generic/fminimum.rs | 3 +- .../libm/src/math/generic/fminimum_num.rs | 3 +- 9 files changed, 330 insertions(+), 63 deletions(-) diff --git a/library/compiler-builtins/libm/src/math/fmin_fmax.rs b/library/compiler-builtins/libm/src/math/fmin_fmax.rs index c4c1b0435dd2..ead9e6599f1b 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 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_ONE, F::NEG_ONE), @@ -95,6 +100,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ZERO), (F::ONE, F::NEG_ZERO, F::NEG_ZERO), (F::ONE, F::ONE, F::ONE), @@ -103,6 +110,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::NEG_ONE), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ONE), (F::NEG_ONE, F::ONE, F::NEG_ONE), @@ -111,6 +120,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::ZERO), (F::INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::INFINITY, F::ONE, F::ONE), @@ -119,6 +130,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::ONE, F::NEG_INFINITY), @@ -127,6 +140,8 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -140,6 +155,18 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, res) in cases { @@ -147,12 +174,29 @@ fn fmin_spec_test(f: impl Fn(F, F) -> F) { assert_biteq!(val, res, "fmin({}, {})", Hexf(x), Hexf(y)); } - // Ordering between zeros and NaNs does not matter + // Ordering between zeros does not matter assert_eq!(f(F::ZERO, F::NEG_ZERO), F::ZERO); assert_eq!(f(F::NEG_ZERO, F::ZERO), F::ZERO); - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); } #[test] @@ -186,6 +230,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::ZERO), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::ONE), (F::NEG_ZERO, F::NEG_ONE, F::NEG_ZERO), @@ -193,6 +239,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_ZERO), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ONE), (F::ONE, F::NEG_ZERO, F::ONE), (F::ONE, F::ONE, F::ONE), @@ -201,6 +249,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::ONE), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::ZERO), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ONE, F::ONE, F::ONE), @@ -209,6 +259,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_ONE), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::INFINITY), (F::INFINITY, F::NEG_ZERO, F::INFINITY), (F::INFINITY, F::ONE, F::INFINITY), @@ -217,6 +269,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::ZERO), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_INFINITY, F::ONE, F::ONE), @@ -225,6 +279,8 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -238,19 +294,54 @@ fn fmax_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, res) in cases { let val = f(x, y); - assert_biteq!(val, res, "fmax({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + val, + res, + "fmax({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between zeros and NaNs does not matter + // Ordering between zeros assert_eq!(f(F::ZERO, F::NEG_ZERO), F::ZERO); assert_eq!(f(F::NEG_ZERO, F::ZERO), F::ZERO); - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::SNAN, F::SNAN).is_nan()); } #[test] diff --git a/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs b/library/compiler-builtins/libm/src/math/fminimum_fmaximum.rs index a3c9c9c3991b..ffc724e3a8d7 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 @@ fn fminimum_spec_test(f: impl Fn(F, F) -> F) { (F::NAN, F::INFINITY, F::NAN), (F::NAN, F::NEG_INFINITY, F::NAN), (F::NAN, F::NAN, F::NAN), + (F::NAN, F::SNAN, F::NAN), ]; for (x, y, res) in cases { let val = f(x, y); - assert_biteq!(val, res, "fminimum({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + val, + res, + "fminimum({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::ONE, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ONE, F::NEG_NAN).is_nan()); - assert!(f(F::INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::ONE).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ONE).is_nan()); - assert!(f(F::NEG_NAN, F::INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // On platforms where operations only return a single canonical NaN (e.g. RISC-V), the + // result may not exactly match one of the inputs which is fine. + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_ONE, F::SNAN,).is_nan()); + assert!(f(F::NEG_SNAN, F::INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ZERO).is_nan()); + assert!(f(F::NEG_SNAN, F::ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::ZERO).is_nan()); + assert!(f(F::NEG_ZERO, F::SNAN,).is_nan()); + assert!(f(F::ONE, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_ONE,).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN,).is_nan()); + assert!(f(F::SNAN, F::NEG_ZERO,).is_nan()); + assert!(f(F::SNAN, F::ONE,).is_nan()); + assert!(f(F::SNAN, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::ZERO,).is_nan()); + assert!(f(F::ZERO, F::SNAN,).is_nan()); } #[test] @@ -220,29 +255,63 @@ fn fmaximum_spec_test(f: impl Fn(F, F) -> F) { (F::NAN, F::INFINITY, F::NAN), (F::NAN, F::NEG_INFINITY, F::NAN), (F::NAN, F::NAN, F::NAN), + (F::NAN, F::SNAN, F::NAN), ]; for (x, y, res) in cases { let val = f(x, y); - assert_biteq!(val, res, "fmaximum({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + val, + res, + "fmaximum({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ZERO, F::NEG_NAN).is_nan()); - assert!(f(F::ONE, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_ONE, F::NEG_NAN).is_nan()); - assert!(f(F::INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ZERO).is_nan()); - assert!(f(F::NEG_NAN, F::ONE).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_ONE).is_nan()); - assert!(f(F::NEG_NAN, F::INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // On platforms where operations only return a single canonical NaN (e.g. RISC-V), the + // result may not exactly match one of the inputs which is fine. + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ZERO, F::NEG_NAN).is_qnan()); + assert!(f(F::ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_ONE, F::NEG_NAN).is_qnan()); + assert!(f(F::INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_INFINITY, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ZERO).is_qnan()); + assert!(f(F::NEG_NAN, F::ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_ONE).is_qnan()); + assert!(f(F::NEG_NAN, F::INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_INFINITY).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_INFINITY, F::SNAN,).is_nan()); + assert!(f(F::NEG_ONE, F::SNAN,).is_nan()); + assert!(f(F::NEG_SNAN, F::INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_INFINITY).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_ZERO).is_nan()); + assert!(f(F::NEG_SNAN, F::ONE).is_nan()); + assert!(f(F::NEG_SNAN, F::ZERO).is_nan()); + assert!(f(F::NEG_ZERO, F::SNAN,).is_nan()); + assert!(f(F::ONE, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_INFINITY,).is_nan()); + assert!(f(F::SNAN, F::NEG_ONE,).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN,).is_nan()); + assert!(f(F::SNAN, F::NEG_ZERO,).is_nan()); + assert!(f(F::SNAN, F::ONE,).is_nan()); + assert!(f(F::SNAN, F::SNAN,).is_nan()); + assert!(f(F::SNAN, F::ZERO,).is_nan()); + assert!(f(F::ZERO, F::SNAN,).is_nan()); } #[test] 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 612cefe756e3..3157f8a3fee8 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 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::NEG_ZERO), @@ -89,6 +92,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ZERO), (F::ONE, F::NEG_ZERO, F::NEG_ZERO), (F::ONE, F::ONE, F::ONE), @@ -97,6 +102,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::NEG_ONE), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ONE), (F::NEG_ONE, F::ONE, F::NEG_ONE), @@ -105,6 +112,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::ZERO), (F::INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::INFINITY, F::ONE, F::ONE), @@ -113,6 +122,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_INFINITY), (F::NEG_INFINITY, F::ONE, F::NEG_INFINITY), @@ -121,6 +132,8 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -134,17 +147,52 @@ fn fminimum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, expected) in cases { let actual = f(x, y); - assert_biteq!(actual, expected, "fminimum_num({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + actual, + expected, + "fminimum_num({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::SNAN, F::SNAN).is_nan()); } #[test] @@ -179,6 +227,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ZERO, F::NEG_INFINITY, F::ZERO), (F::ZERO, F::NAN, F::ZERO), (F::ZERO, F::NEG_NAN, F::ZERO), + (F::ZERO, F::SNAN, F::ZERO), + (F::ZERO, F::NEG_SNAN, F::ZERO), (F::NEG_ZERO, F::ZERO, F::ZERO), (F::NEG_ZERO, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ZERO, F::ONE, F::ONE), @@ -187,6 +237,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ZERO, F::NEG_INFINITY, F::NEG_ZERO), (F::NEG_ZERO, F::NAN, F::NEG_ZERO), (F::NEG_ZERO, F::NEG_NAN, F::NEG_ZERO), + (F::NEG_ZERO, F::SNAN, F::NEG_ZERO), + (F::NEG_ZERO, F::NEG_SNAN, F::NEG_ZERO), (F::ONE, F::ZERO, F::ONE), (F::ONE, F::NEG_ZERO, F::ONE), (F::ONE, F::ONE, F::ONE), @@ -195,6 +247,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::ONE, F::NEG_INFINITY, F::ONE), (F::ONE, F::NAN, F::ONE), (F::ONE, F::NEG_NAN, F::ONE), + (F::ONE, F::SNAN, F::ONE), + (F::ONE, F::NEG_SNAN, F::ONE), (F::NEG_ONE, F::ZERO, F::ZERO), (F::NEG_ONE, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_ONE, F::ONE, F::ONE), @@ -203,6 +257,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_ONE, F::NEG_INFINITY, F::NEG_ONE), (F::NEG_ONE, F::NAN, F::NEG_ONE), (F::NEG_ONE, F::NEG_NAN, F::NEG_ONE), + (F::NEG_ONE, F::SNAN, F::NEG_ONE), + (F::NEG_ONE, F::NEG_SNAN, F::NEG_ONE), (F::INFINITY, F::ZERO, F::INFINITY), (F::INFINITY, F::NEG_ZERO, F::INFINITY), (F::INFINITY, F::ONE, F::INFINITY), @@ -211,6 +267,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::INFINITY, F::NEG_INFINITY, F::INFINITY), (F::INFINITY, F::NAN, F::INFINITY), (F::INFINITY, F::NEG_NAN, F::INFINITY), + (F::INFINITY, F::SNAN, F::INFINITY), + (F::INFINITY, F::NEG_SNAN, F::INFINITY), (F::NEG_INFINITY, F::ZERO, F::ZERO), (F::NEG_INFINITY, F::NEG_ZERO, F::NEG_ZERO), (F::NEG_INFINITY, F::ONE, F::ONE), @@ -219,6 +277,8 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_INFINITY, F::NEG_INFINITY, F::NEG_INFINITY), (F::NEG_INFINITY, F::NAN, F::NEG_INFINITY), (F::NEG_INFINITY, F::NEG_NAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::SNAN, F::NEG_INFINITY), + (F::NEG_INFINITY, F::NEG_SNAN, F::NEG_INFINITY), (F::NAN, F::ZERO, F::ZERO), (F::NAN, F::NEG_ZERO, F::NEG_ZERO), (F::NAN, F::ONE, F::ONE), @@ -232,17 +292,52 @@ fn fmaximum_num_spec_test(f: impl Fn(F, F) -> F) { (F::NEG_NAN, F::NEG_ONE, F::NEG_ONE), (F::NEG_NAN, F::INFINITY, F::INFINITY), (F::NEG_NAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::SNAN, F::ZERO, F::ZERO), + (F::SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::SNAN, F::ONE, F::ONE), + (F::SNAN, F::NEG_ONE, F::NEG_ONE), + (F::SNAN, F::INFINITY, F::INFINITY), + (F::SNAN, F::NEG_INFINITY, F::NEG_INFINITY), + (F::NEG_SNAN, F::ZERO, F::ZERO), + (F::NEG_SNAN, F::NEG_ZERO, F::NEG_ZERO), + (F::NEG_SNAN, F::ONE, F::ONE), + (F::NEG_SNAN, F::NEG_ONE, F::NEG_ONE), + (F::NEG_SNAN, F::INFINITY, F::INFINITY), + (F::NEG_SNAN, F::NEG_INFINITY, F::NEG_INFINITY), ]; for (x, y, expected) in cases { let actual = f(x, y); - assert_biteq!(actual, expected, "fmaximum_num({}, {})", Hexf(x), Hexf(y)); + assert_biteq!( + actual, + expected, + "fmaximum_num({}, {}) ({}, {})", + Hexf(x), + Hexf(y), + Hexi(x.to_bits()), + Hexi(y.to_bits()), + ); } - // Ordering between NaNs does not matter - assert!(f(F::NAN, F::NEG_NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NAN).is_nan()); - assert!(f(F::NEG_NAN, F::NEG_NAN).is_nan()); + // Selection between NaNs does not matter, it just must be quiet + assert!(f(F::NAN, F::NEG_NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NAN).is_qnan()); + assert!(f(F::NEG_NAN, F::NEG_NAN).is_qnan()); + + // These operations should technically return a qnan, but LLVM optimizes out our + // `* 1.0` canonicalization. + assert!(f(F::NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_NAN, F::SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::NEG_SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::NEG_SNAN, F::SNAN).is_nan()); + assert!(f(F::SNAN, F::NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_NAN).is_nan()); + assert!(f(F::SNAN, F::NEG_SNAN).is_nan()); + assert!(f(F::SNAN, F::SNAN).is_nan()); } #[test] diff --git a/library/compiler-builtins/libm/src/math/generic/fmax.rs b/library/compiler-builtins/libm/src/math/generic/fmax.rs index b05804704d03..bdd46cc38a81 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 55a031e18ee8..1fa48f964804 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 2dc60b2d237f..c7ca50a89dc8 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 e2245bf9e137..e7755e82345c 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 aa68b1291d42..82e72d644064 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 265bd4605ce3..5b5271b123ba 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;