From 7405e638ee3b328c6cc5b2dac45c99f9d4d8286f Mon Sep 17 00:00:00 2001 From: sunteng Date: Thu, 4 Jun 2026 21:16:32 +0800 Subject: [PATCH] feat: add asin/acos intrinsic support for f32 and f64 Signed-off-by: sunteng --- crates/dialect-mir/src/rust_intrinsics.rs | 8 + .../terminator/intrinsics/float_math.rs | 45 ++ crates/mir-lower/src/convert/ops/call.rs | 32 ++ .../examples/asin_acos_smoke/Cargo.lock | 424 ++++++++++++++++++ .../examples/asin_acos_smoke/Cargo.toml | 13 + .../examples/asin_acos_smoke/LICENSE | 190 ++++++++ .../examples/asin_acos_smoke/README.md | 40 ++ .../examples/asin_acos_smoke/src/main.rs | 215 +++++++++ crates/rustc-codegen-cuda/src/collector.rs | 6 +- 9 files changed, 972 insertions(+), 1 deletion(-) create mode 100644 crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.lock create mode 100644 crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.toml create mode 100644 crates/rustc-codegen-cuda/examples/asin_acos_smoke/LICENSE create mode 100644 crates/rustc-codegen-cuda/examples/asin_acos_smoke/README.md create mode 100644 crates/rustc-codegen-cuda/examples/asin_acos_smoke/src/main.rs diff --git a/crates/dialect-mir/src/rust_intrinsics.rs b/crates/dialect-mir/src/rust_intrinsics.rs index b125361c57..6735ad0a58 100644 --- a/crates/dialect-mir/src/rust_intrinsics.rs +++ b/crates/dialect-mir/src/rust_intrinsics.rs @@ -140,6 +140,14 @@ pub const CALLEE_MINNUM_NSZ_F32: &str = placeholder!("minimum_number_nsz_f32"); /// Placeholder call used for `core::intrinsics::minimum_number_nsz_f64` /// (the intrinsic backing `f64::min`). pub const CALLEE_MINNUM_NSZ_F64: &str = placeholder!("minimum_number_nsz_f64"); +/// Placeholder call used for `f32::asin` / `std::sys::cmath::asinf`. +pub const CALLEE_ASIN_F32: &str = placeholder!("asinf32"); +/// Placeholder call used for `f64::asin` / `std::sys::cmath::asin`. +pub const CALLEE_ASIN_F64: &str = placeholder!("asinf64"); +/// Placeholder call used for `f32::acos` / `std::sys::cmath::acosf`. +pub const CALLEE_ACOS_F32: &str = placeholder!("acosf32"); +/// Placeholder call used for `f64::acos` / `std::sys::cmath::acos`. +pub const CALLEE_ACOS_F64: &str = placeholder!("acosf64"); /// Placeholder call used for `f32::atan2` / `std::sys::cmath::atan2f`. pub const CALLEE_ATAN2_F32: &str = placeholder!("atan2f32"); /// Placeholder call used for `f64::atan2` / `std::sys::cmath::atan2`. diff --git a/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs b/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs index 7d9808e532..bb1591df8c 100644 --- a/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs +++ b/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs @@ -105,6 +105,14 @@ pub enum RustFloatMathIntrinsic { MinNumNszF32, /// `core::intrinsics::minimum_number_nsz_f64` (backs `f64::min`). MinNumNszF64, + /// `f32::asin` / `std::sys::cmath::asinf`. + AsinF32, + /// `f64::asin` / `std::sys::cmath::asin`. + AsinF64, + /// `f32::acos` / `std::sys::cmath::acosf`. + AcosF32, + /// `f64::acos` / `std::sys::cmath::acos`. + AcosF64, /// `f32::atan2` / `std::sys::cmath::atan2f`. Atan2F32, /// `f64::atan2` / `std::sys::cmath::atan2`. @@ -182,6 +190,10 @@ impl RustFloatMathIntrinsic { | "std::intrinsics::minimum_number_nsz_f32" => Some(Self::MinNumNszF32), "core::intrinsics::minimum_number_nsz_f64" | "std::intrinsics::minimum_number_nsz_f64" => Some(Self::MinNumNszF64), + "std::sys::cmath::asinf" => Some(Self::AsinF32), + "std::sys::cmath::asin" => Some(Self::AsinF64), + "std::sys::cmath::acosf" => Some(Self::AcosF32), + "std::sys::cmath::acos" => Some(Self::AcosF64), "std::sys::cmath::atan2f" => Some(Self::Atan2F32), "std::sys::cmath::atan2" => Some(Self::Atan2F64), "std::sys::cmath::atanf" => Some(Self::AtanF32), @@ -249,6 +261,10 @@ impl RustFloatMathIntrinsic { "fmax" => Some(Self::MaxNumNszF64), "fminf" => Some(Self::MinNumNszF32), "fmin" => Some(Self::MinNumNszF64), + "asinf" => Some(Self::AsinF32), + "asin" => Some(Self::AsinF64), + "acosf" => Some(Self::AcosF32), + "acos" => Some(Self::AcosF64), "atan2f" => Some(Self::Atan2F32), "atan2" => Some(Self::Atan2F64), "atanf" => Some(Self::AtanF32), @@ -305,6 +321,10 @@ impl RustFloatMathIntrinsic { Self::MaxNumNszF64 => rust_intrinsics::CALLEE_MAXNUM_NSZ_F64, Self::MinNumNszF32 => rust_intrinsics::CALLEE_MINNUM_NSZ_F32, Self::MinNumNszF64 => rust_intrinsics::CALLEE_MINNUM_NSZ_F64, + Self::AsinF32 => rust_intrinsics::CALLEE_ASIN_F32, + Self::AsinF64 => rust_intrinsics::CALLEE_ASIN_F64, + Self::AcosF32 => rust_intrinsics::CALLEE_ACOS_F32, + Self::AcosF64 => rust_intrinsics::CALLEE_ACOS_F64, Self::Atan2F32 => rust_intrinsics::CALLEE_ATAN2_F32, Self::Atan2F64 => rust_intrinsics::CALLEE_ATAN2_F64, Self::AtanF32 => rust_intrinsics::CALLEE_ATAN_F32, @@ -595,6 +615,31 @@ mod tests { } } + /// `asin`/`acos` reach libdevice from both the std `f{32,64}` inherent + /// methods (`std::sys::cmath::*`) and the `libm` crate spellings glam's + /// `nostd-libm` lowering emits on nvptx. Unlike `cbrt` these are std-only + /// (no `core::num::imp::libm` form), so only the cmath and libm-crate + /// spellings exist; both must map to the same `__nv_a{sin,cos}{,f}` path. + #[test] + fn from_core_path_recognizes_asin_acos_via_cmath_and_libm() { + for (path, expected) in [ + ("std::sys::cmath::asinf", RustFloatMathIntrinsic::AsinF32), + ("std::sys::cmath::asin", RustFloatMathIntrinsic::AsinF64), + ("std::sys::cmath::acosf", RustFloatMathIntrinsic::AcosF32), + ("std::sys::cmath::acos", RustFloatMathIntrinsic::AcosF64), + ("libm::asinf", RustFloatMathIntrinsic::AsinF32), + ("libm::math::asin::asin", RustFloatMathIntrinsic::AsinF64), + ("libm::acosf", RustFloatMathIntrinsic::AcosF32), + ("libm::math::acos::acos", RustFloatMathIntrinsic::AcosF64), + ] { + assert_eq!( + RustFloatMathIntrinsic::from_core_path(path), + Some(expected), + "`{path}` did not map to the expected asin/acos intrinsic" + ); + } + } + /// Libm interception must be anchored to the `libm` crate root. A user /// function that shares a libm function name, inside a path that merely /// mentions "libm", must stay a regular call: a bare `contains("libm")` diff --git a/crates/mir-lower/src/convert/ops/call.rs b/crates/mir-lower/src/convert/ops/call.rs index 50903bf817..a9953b64ae 100644 --- a/crates/mir-lower/src/convert/ops/call.rs +++ b/crates/mir-lower/src/convert/ops/call.rs @@ -215,6 +215,10 @@ enum RustFloatMathIntrinsic { MaxNumNszF64, MinNumNszF32, MinNumNszF64, + AsinF32, + AsinF64, + AcosF32, + AcosF64, Atan2F32, Atan2F64, AtanF32, @@ -270,6 +274,10 @@ impl RustFloatMathIntrinsic { rust_intrinsics::CALLEE_MAXNUM_NSZ_F64 => Some(Self::MaxNumNszF64), rust_intrinsics::CALLEE_MINNUM_NSZ_F32 => Some(Self::MinNumNszF32), rust_intrinsics::CALLEE_MINNUM_NSZ_F64 => Some(Self::MinNumNszF64), + rust_intrinsics::CALLEE_ASIN_F32 => Some(Self::AsinF32), + rust_intrinsics::CALLEE_ASIN_F64 => Some(Self::AsinF64), + rust_intrinsics::CALLEE_ACOS_F32 => Some(Self::AcosF32), + rust_intrinsics::CALLEE_ACOS_F64 => Some(Self::AcosF64), rust_intrinsics::CALLEE_ATAN2_F32 => Some(Self::Atan2F32), rust_intrinsics::CALLEE_ATAN2_F64 => Some(Self::Atan2F64), rust_intrinsics::CALLEE_ATAN_F32 => Some(Self::AtanF32), @@ -336,6 +344,10 @@ impl RustFloatMathIntrinsic { Self::MaxNumNszF64 => Ok("__nv_fmax"), Self::MinNumNszF32 => Ok("__nv_fminf"), Self::MinNumNszF64 => Ok("__nv_fmin"), + Self::AsinF32 => Ok("__nv_asinf"), + Self::AsinF64 => Ok("__nv_asin"), + Self::AcosF32 => Ok("__nv_acosf"), + Self::AcosF64 => Ok("__nv_acos"), Self::Atan2F32 => Ok("__nv_atan2f"), Self::Atan2F64 => Ok("__nv_atan2"), Self::AtanF32 => Ok("__nv_atanf"), @@ -1378,6 +1390,22 @@ mod tests { rust_intrinsics::CALLEE_MINNUM_NSZ_F64, RustFloatMathIntrinsic::MinNumNszF64, ), + ( + rust_intrinsics::CALLEE_ASIN_F32, + RustFloatMathIntrinsic::AsinF32, + ), + ( + rust_intrinsics::CALLEE_ASIN_F64, + RustFloatMathIntrinsic::AsinF64, + ), + ( + rust_intrinsics::CALLEE_ACOS_F32, + RustFloatMathIntrinsic::AcosF32, + ), + ( + rust_intrinsics::CALLEE_ACOS_F64, + RustFloatMathIntrinsic::AcosF64, + ), ( rust_intrinsics::CALLEE_ATAN2_F32, RustFloatMathIntrinsic::Atan2F32, @@ -1426,6 +1454,10 @@ mod tests { assert_eq!(RustFloatMathIntrinsic::PowiF32.arg_count(), 2); assert_eq!(RustFloatMathIntrinsic::PowfF64.arg_count(), 2); assert_eq!(RustFloatMathIntrinsic::CopysignF32.arg_count(), 2); + assert_eq!(RustFloatMathIntrinsic::AsinF32.arg_count(), 1); + assert_eq!(RustFloatMathIntrinsic::AsinF64.arg_count(), 1); + assert_eq!(RustFloatMathIntrinsic::AcosF32.arg_count(), 1); + assert_eq!(RustFloatMathIntrinsic::AcosF64.arg_count(), 1); assert_eq!(RustFloatMathIntrinsic::Atan2F32.arg_count(), 2); assert_eq!(RustFloatMathIntrinsic::Atan2F64.arg_count(), 2); assert_eq!(RustFloatMathIntrinsic::AtanF32.arg_count(), 1); diff --git a/crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.lock b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.lock new file mode 100644 index 0000000000..259f623579 --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.lock @@ -0,0 +1,424 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "asin_acos_smoke" +version = "0.1.0" +dependencies = [ + "cuda-core", + "cuda-device", + "cuda-host", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cuda-bindings" +version = "0.2.1" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cuda-core" +version = "0.2.1" +dependencies = [ + "anyhow", + "cuda-bindings", + "half", + "oxide-artifacts", +] + +[[package]] +name = "cuda-device" +version = "0.2.1" +dependencies = [ + "cuda-macros", +] + +[[package]] +name = "cuda-host" +version = "0.2.1" +dependencies = [ + "cuda-core", + "cuda-macros", + "half", + "libnvvm-sys", + "nvjitlink-sys", + "thiserror", +] + +[[package]] +name = "cuda-macros" +version = "0.2.1" +dependencies = [ + "proc-macro2", + "quote", + "reserved-oxide-symbols", + "syn", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[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.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libnvvm-sys" +version = "0.2.1" +dependencies = [ + "libloading", + "thiserror", +] + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nvjitlink-sys" +version = "0.2.1" +dependencies = [ + "libloading", + "thiserror", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap", + "memchr", +] + +[[package]] +name = "oxide-artifacts" +version = "0.2.1" +dependencies = [ + "object", +] + +[[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-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.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reserved-oxide-symbols" +version = "0.2.1" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.toml b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.toml new file mode 100644 index 0000000000..029e9efba1 --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "asin_acos_smoke" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +# Standalone crate (not part of parent workspace). +[workspace] + +[dependencies] +cuda-device = { path = "../../../cuda-device" } +cuda-host = { path = "../../../cuda-host" } +cuda-core = { path = "../../../cuda-core" } diff --git a/crates/rustc-codegen-cuda/examples/asin_acos_smoke/LICENSE b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/LICENSE new file mode 100644 index 0000000000..7cdf362c1a --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2024-2026 NVIDIA CORPORATION + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/crates/rustc-codegen-cuda/examples/asin_acos_smoke/README.md b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/README.md new file mode 100644 index 0000000000..c131aed4b6 --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/README.md @@ -0,0 +1,40 @@ +# asin_acos_smoke + +## `asin`/`acos` via libdevice + +This example demonstrates `f32::asin`/`acos` and `f64::asin`/`acos` in +device code lowering to NVIDIA libdevice (`__nv_asinf`, `__nv_asin`, +`__nv_acosf`, `__nv_acos`). + +## What This Example Does + +- Four small kernels — `asin`/`acos` × `f32`/`f64` — compute `x.asin()` / + `x.acos()` per element using the native Rust method syntax. +- The host runs the kernels over 14 inputs spanning the full `[-1, 1]` + domain (including both endpoints and `-0.0`), then compares each result + against the same expression evaluated with stdlib `f{32,64}::asin`/`acos` + on the host. +- The domain endpoints are the interesting cases: `asin(±1) = ±pi/2`, + `acos(-1) = pi`, `acos(1) = 0`. +- Tolerance: 2 ULP, matching the bound `math_atan` / `cbrt_smoke` / + `primitive_stress` use for the other libdevice transcendentals. + +Unlike `std::sys::cmath`, `asin`/`acos` have no `core` libm form (they are +std-only), so the inherent methods lower straight through the cmath shims. +The `libm`-crate spellings (`libm::asinf`, ...) glam emits on nvptx route to +the same libdevice path; those are covered by the importer unit tests. + +Exits 0 on PASS, 1 on FAIL. + +## Pipeline + +Because the kernels emit `__nv_*` calls, the cuda-oxide pipeline stops at +NVVM-IR (skipping `llc`). `ltoir::load_kernel_module` then drives libNVVM +(linking `libdevice.10.bc`) and nvJitLink to produce a cubin on first +launch. + +## Run + +```bash +cargo oxide run asin_acos_smoke +``` diff --git a/crates/rustc-codegen-cuda/examples/asin_acos_smoke/src/main.rs b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/src/main.rs new file mode 100644 index 0000000000..3f6e11131e --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/asin_acos_smoke/src/main.rs @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Smoke test for `f32::asin`/`acos` / `f64::asin`/`acos` → libdevice lowering. +//! +//! `asin`/`acos` are std-only (no `core` libm form): the inherent methods +//! lower to the `std::sys::cmath::asinf` / `asin` / `acosf` / `acos` extern +//! declarations, which is the call shape device codegen sees in MIR. Before +//! asin/acos support was added, cuda-oxide had no handler for those paths +//! and the calls would fall out of the pipeline as an unresolved symbol. +//! After the matching entries were added to: +//! +//! * `dialect-mir::rust_intrinsics` +//! * `mir-importer::translator::terminator::intrinsics::float_math` +//! (both the `std::sys::cmath` and `libm`-crate spellings) +//! * `mir-lower::convert::ops::call` +//! * `rustc-codegen-cuda::collector` (std cmath shim allowlist) +//! +//! the calls lower to libdevice `__nv_asinf` / `__nv_asin` / `__nv_acosf` / +//! `__nv_acos`, which the auto-detected libNVVM + nvJitLink pipeline +//! resolves transparently. +//! +//! The host computes the same expression with stdlib `f{32,64}::asin`/`acos` +//! and compares within a 2-ULP tolerance (matching the bound `math_atan`, +//! `cbrt_smoke` and `primitive_stress` use for the other libdevice +//! transcendentals). Inputs span the full `[-1, 1]` domain including both +//! endpoints: `asin(±1) = ±pi/2` and `acos(-1) = pi`, `acos(1) = 0` are the +//! interesting boundary cases. +//! +//! Run: +//! cargo oxide run asin_acos_smoke +//! +//! Exits 0 on PASS, 1 on FAIL. + +use cuda_core::{CudaContext, DeviceBuffer, LaunchConfig}; +use cuda_device::{DisjointSlice, kernel, thread}; +use cuda_host::{cuda_module, ltoir}; + +#[cuda_module] +mod kernels { + use super::*; + + #[kernel] + pub fn asin_f32(xs: &[f32], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if i < xs.len() + && let Some(slot) = out.get_mut(idx) + { + *slot = xs[i].asin(); + } + } + + #[kernel] + pub fn acos_f32(xs: &[f32], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if i < xs.len() + && let Some(slot) = out.get_mut(idx) + { + *slot = xs[i].acos(); + } + } + + #[kernel] + pub fn asin_f64(xs: &[f64], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if i < xs.len() + && let Some(slot) = out.get_mut(idx) + { + *slot = xs[i].asin(); + } + } + + #[kernel] + pub fn acos_f64(xs: &[f64], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if i < xs.len() + && let Some(slot) = out.get_mut(idx) + { + *slot = xs[i].acos(); + } + } +} + +/// IEEE-754 ULP distance for finite operands of a given width. +/// `asin`/`acos` of inputs in `[-1, 1]` land in `[-pi/2, pi]`, so we don't +/// need to handle NaN/Inf here. +fn ulp_distance(a_bits: u64, b_bits: u64, sign_mask: u64, body_mask: u64) -> u64 { + let map = |bits: u64| { + if bits & sign_mask != 0 { + sign_mask - (bits & body_mask) + } else { + sign_mask + (bits & body_mask) + } + }; + map(a_bits).abs_diff(map(b_bits)) +} + +fn ulp_diff_f32(a: f32, b: f32) -> u64 { + ulp_distance( + a.to_bits() as u64, + b.to_bits() as u64, + 0x8000_0000, + 0x7FFF_FFFF, + ) +} + +fn ulp_diff_f64(a: f64, b: f64) -> u64 { + ulp_distance( + a.to_bits(), + b.to_bits(), + 0x8000_0000_0000_0000, + 0x7FFF_FFFF_FFFF_FFFF, + ) +} + +fn main() -> Result<(), Box> { + println!("=== asin_acos_smoke: f32/f64 asin/acos via libdevice ===\n"); + + let ctx = CudaContext::new(0)?; + let stream = ctx.default_stream(); + // `__nv_*` calls in the kernel force the NVVM-IR output flavor; the + // first launch builds a cubin via libNVVM + nvJitLink. + let module = ltoir::load_kernel_module(&ctx, "asin_acos_smoke")?; + let module = kernels::from_module(module)?; + + // Full `[-1, 1]` domain including both endpoints. Values are + // f32-representable so the same array doubles as f64 input after a + // widening cast. + let xs_f32: Vec = vec![ + -1.0, -0.9, -0.75, -0.5, -0.25, -0.1, -0.0, 0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0, + ]; + let xs_f64: Vec = xs_f32.iter().map(|&v| v as f64).collect(); + let n = xs_f32.len(); + let cfg = LaunchConfig::for_num_elems(n as u32); + + let xs32 = DeviceBuffer::from_host(&stream, &xs_f32)?; + let xs64 = DeviceBuffer::from_host(&stream, &xs_f64)?; + + let mut out_asin_f32 = DeviceBuffer::::zeroed(&stream, n)?; + let mut out_acos_f32 = DeviceBuffer::::zeroed(&stream, n)?; + let mut out_asin_f64 = DeviceBuffer::::zeroed(&stream, n)?; + let mut out_acos_f64 = DeviceBuffer::::zeroed(&stream, n)?; + + module.asin_f32(&stream, cfg, &xs32, &mut out_asin_f32)?; + module.acos_f32(&stream, cfg, &xs32, &mut out_acos_f32)?; + module.asin_f64(&stream, cfg, &xs64, &mut out_asin_f64)?; + module.acos_f64(&stream, cfg, &xs64, &mut out_acos_f64)?; + + let got_asin_f32 = out_asin_f32.to_host_vec(&stream)?; + let got_acos_f32 = out_acos_f32.to_host_vec(&stream)?; + let got_asin_f64 = out_asin_f64.to_host_vec(&stream)?; + let got_acos_f64 = out_acos_f64.to_host_vec(&stream)?; + + // libdevice transcendentals are typically within 1 ULP of host libm; + // 2 ULP matches the bound `math_atan` / `cbrt_smoke` / `primitive_stress` + // use for the other libdevice transcendentals. + const ULP_LIMIT: u64 = 2; + + let mut failures = 0usize; + for i in 0..n { + let exp_asin_f32 = xs_f32[i].asin(); + let exp_acos_f32 = xs_f32[i].acos(); + let exp_asin_f64 = xs_f64[i].asin(); + let exp_acos_f64 = xs_f64[i].acos(); + + let d_asin_f32 = ulp_diff_f32(got_asin_f32[i], exp_asin_f32); + let d_acos_f32 = ulp_diff_f32(got_acos_f32[i], exp_acos_f32); + let d_asin_f64 = ulp_diff_f64(got_asin_f64[i], exp_asin_f64); + let d_acos_f64 = ulp_diff_f64(got_acos_f64[i], exp_acos_f64); + + let ok = d_asin_f32 <= ULP_LIMIT + && d_acos_f32 <= ULP_LIMIT + && d_asin_f64 <= ULP_LIMIT + && d_acos_f64 <= ULP_LIMIT; + + if !ok { + failures += 1; + if failures <= 8 { + eprintln!( + "[{i}] x={:>6.3} | \ + asin_f32 ulp={d_asin_f32} acos_f32 ulp={d_acos_f32} | \ + asin_f64 ulp={d_asin_f64} acos_f64 ulp={d_acos_f64}", + xs_f32[i], + ); + } + } + } + + // A handful of representative samples printed regardless of pass/fail + // (left endpoint, zero, right endpoint). + for &i in &[0usize, 7, 13] { + println!( + "[{i}] x={x:>6.3} asin_f32 gpu={a32} cpu={ea32} acos_f32 gpu={c32} cpu={ec32}", + x = xs_f32[i], + a32 = got_asin_f32[i], + ea32 = xs_f32[i].asin(), + c32 = got_acos_f32[i], + ec32 = xs_f32[i].acos(), + ); + } + + if failures == 0 { + println!("\nSUCCESS: {n} cases × 4 variants within {ULP_LIMIT} ULP of host libm"); + Ok(()) + } else { + eprintln!("\nFAILED: {failures}/{n} cases out of tolerance"); + std::process::exit(1); + } +} diff --git a/crates/rustc-codegen-cuda/src/collector.rs b/crates/rustc-codegen-cuda/src/collector.rs index bf013d3a0b..b9d6593e52 100644 --- a/crates/rustc-codegen-cuda/src/collector.rs +++ b/crates/rustc-codegen-cuda/src/collector.rs @@ -433,7 +433,11 @@ pub fn is_fully_monomorphized<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) fn is_intrinsic_lowered_cmath_shim(fn_path: &str) -> bool { matches!( fn_path, - "std::sys::cmath::atan2f" + "std::sys::cmath::asinf" + | "std::sys::cmath::asin" + | "std::sys::cmath::acosf" + | "std::sys::cmath::acos" + | "std::sys::cmath::atan2f" | "std::sys::cmath::atan2" | "std::sys::cmath::atanf" | "std::sys::cmath::atan"