diff --git a/cuda-oxide/crates/dialect-mir/src/rust_intrinsics.rs b/cuda-oxide/crates/dialect-mir/src/rust_intrinsics.rs index d543f73680..e2849e8f4c 100644 --- a/cuda-oxide/crates/dialect-mir/src/rust_intrinsics.rs +++ b/cuda-oxide/crates/dialect-mir/src/rust_intrinsics.rs @@ -52,6 +52,13 @@ pub const CALLEE_SATURATING_SUB: &str = placeholder!("saturating_sub"); /// chunk count with `exact_div(self.len(), N)`. pub const CALLEE_EXACT_DIV: &str = placeholder!("exact_div"); +/// Placeholder call used for `core::intrinsics::float_to_int_unchecked`. +/// +/// Float-to-integer conversion where the caller guarantees the value is finite +/// and fits the destination type once truncated. Backs `f32::to_int_unchecked` +/// and `f64::to_int_unchecked`. +pub const CALLEE_FLOAT_TO_INT_UNCHECKED: &str = placeholder!("float_to_int_unchecked"); + /// Placeholder call used for `core::intrinsics::carrying_mul_add`. /// Backs the bigint helper methods `carrying_mul_add`, `carrying_mul`, /// and `widening_mul` on integer types. @@ -105,6 +112,7 @@ pub fn is_known_placeholder(callee: &str) -> bool { | CALLEE_SATURATING_ADD | CALLEE_SATURATING_SUB | CALLEE_EXACT_DIV + | CALLEE_FLOAT_TO_INT_UNCHECKED | CALLEE_CARRYING_MUL_ADD | CALLEE_SELECT_UNPREDICTABLE ) @@ -308,6 +316,7 @@ mod tests { CALLEE_SATURATING_ADD, CALLEE_SATURATING_SUB, CALLEE_EXACT_DIV, + CALLEE_FLOAT_TO_INT_UNCHECKED, CALLEE_CARRYING_MUL_ADD, CALLEE_FADD_FAST, CALLEE_FSUB_FAST, diff --git a/cuda-oxide/crates/mir-importer/README.md b/cuda-oxide/crates/mir-importer/README.md index 6815539205..dcf2d6fa47 100644 --- a/cuda-oxide/crates/mir-importer/README.md +++ b/cuda-oxide/crates/mir-importer/README.md @@ -96,6 +96,7 @@ file: | `debug` | Debug and profiling intrinsics | | `exact_div` | Rust compiler `exact_div` intrinsic | | `float_math` | Rust compiler floating-point math intrinsics | +| `float_to_int` | Rust compiler `float_to_int_unchecked` intrinsic | | `generated` | Generated raw/compatibility path dispatch for CUDA intrinsics | | `iket` | Translation of `cuda_device::iket` compiler markers | | `indexing` | Thread and block indexing intrinsics | diff --git a/cuda-oxide/crates/mir-importer/src/translator/terminator/intrinsics/float_to_int.rs b/cuda-oxide/crates/mir-importer/src/translator/terminator/intrinsics/float_to_int.rs new file mode 100644 index 0000000000..374ebb3ae4 --- /dev/null +++ b/cuda-oxide/crates/mir-importer/src/translator/terminator/intrinsics/float_to_int.rs @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Rust compiler `float_to_int_unchecked` intrinsic. +//! +//! `core::intrinsics::float_to_int_unchecked` converts a float to an integer +//! with a caller promise: the value is not NaN or infinite, and it fits the +//! destination type once truncated toward zero. Violating that is undefined +//! behaviour, which is what lets it lower to a plain `fptosi`/`fptoui` with +//! none of the clamping and NaN handling the saturating `as` cast needs. +//! +//! `f32::to_int_unchecked` and `f64::to_int_unchecked` are the stable entry +//! points; they reach the intrinsic through `FloatToInt::to_int_unchecked` +//! (`core/src/convert/num.rs`). Without support for the intrinsic they fail to +//! translate, so device code had no unchecked float-to-integer conversion. + +use super::super::helpers; +use crate::error::TranslationResult; +use crate::translator::types; +use crate::translator::values::ValueMap; +use dialect_mir::rust_intrinsics; +use pliron::basic_block::BasicBlock; +use pliron::context::{Context, Ptr}; +use pliron::location::Location; +use pliron::operation::Operation; +use rustc_public::mir; + +/// The `float_to_int_unchecked` intrinsic from libcore. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RustFloatToIntIntrinsic { + /// `core::intrinsics::float_to_int_unchecked`. + FloatToIntUnchecked, +} + +impl RustFloatToIntIntrinsic { + /// Recognize the libcore intrinsic path that survived into MIR. + pub fn from_core_path(name: &str) -> Option { + match name { + "core::intrinsics::float_to_int_unchecked" + | "std::intrinsics::float_to_int_unchecked" => Some(Self::FloatToIntUnchecked), + _ => None, + } + } + + /// Return the internal placeholder name used until MIR-to-LLVM lowering. + pub fn placeholder_callee(self) -> &'static str { + match self { + Self::FloatToIntUnchecked => rust_intrinsics::CALLEE_FLOAT_TO_INT_UNCHECKED, + } + } +} + +/// Emit a placeholder `mir.call` for `core::intrinsics::float_to_int_unchecked`. +#[allow(clippy::too_many_arguments)] +pub fn emit_rust_float_to_int_intrinsic( + ctx: &mut Context, + body: &mir::Body, + intrinsic: RustFloatToIntIntrinsic, + args: &[mir::Operand], + destination: &mir::Place, + target: &Option, + block_ptr: Ptr, + prev_op: Option>, + value_map: &mut ValueMap, + block_map: &[Ptr], + loc: Location, +) -> TranslationResult> { + let return_type = types::translate_destination_type(ctx, body, destination, &loc)?; + helpers::emit_function_call( + ctx, + body, + intrinsic.placeholder_callee(), + args, + destination, + return_type, + None, + target, + block_ptr, + prev_op, + value_map, + block_map, + loc, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_core_path_recognizes_float_to_int_unchecked() { + for path in [ + "core::intrinsics::float_to_int_unchecked", + "std::intrinsics::float_to_int_unchecked", + ] { + assert_eq!( + RustFloatToIntIntrinsic::from_core_path(path), + Some(RustFloatToIntIntrinsic::FloatToIntUnchecked), + "expected `{path}` to be recognized" + ); + } + } + + /// Only the intrinsic itself matches. The methods that wrap it have MIR + /// bodies of their own, which the importer translates as ordinary calls. + #[test] + fn from_core_path_rejects_neighbouring_conversion_paths() { + for path in [ + "core::convert::num::FloatToInt::to_int_unchecked", + "f32::to_int_unchecked", + "core::intrinsics::float_to_int", + "core::intrinsics::exact_div", + ] { + assert_eq!( + RustFloatToIntIntrinsic::from_core_path(path), + None, + "expected `{path}` not to be recognized" + ); + } + } + + #[test] + fn placeholder_callee_matches_the_dialect_constant() { + assert_eq!( + RustFloatToIntIntrinsic::FloatToIntUnchecked.placeholder_callee(), + rust_intrinsics::CALLEE_FLOAT_TO_INT_UNCHECKED + ); + } +} diff --git a/cuda-oxide/crates/mir-importer/src/translator/terminator/intrinsics/mod.rs b/cuda-oxide/crates/mir-importer/src/translator/terminator/intrinsics/mod.rs index 5c50b07cde..d24adfcf95 100644 --- a/cuda-oxide/crates/mir-importer/src/translator/terminator/intrinsics/mod.rs +++ b/cuda-oxide/crates/mir-importer/src/translator/terminator/intrinsics/mod.rs @@ -25,6 +25,7 @@ //! | `bitops` | Rust compiler bit-manipulation intrinsics | //! | `exact_div` | Rust compiler `exact_div` | //! | `float_math` | Rust compiler floating-point math intrinsics | +//! | `float_to_int` | Rust compiler `float_to_int_unchecked` | //! | `saturating` | Rust compiler saturating integer intrinsics | //! //! # Architecture @@ -49,6 +50,7 @@ pub mod bitops; pub mod debug; pub mod exact_div; pub mod float_math; +pub mod float_to_int; pub mod generated; pub mod iket; pub mod indexing; diff --git a/cuda-oxide/crates/mir-importer/src/translator/terminator/mod.rs b/cuda-oxide/crates/mir-importer/src/translator/terminator/mod.rs index 0d0d03f646..76de2d8236 100644 --- a/cuda-oxide/crates/mir-importer/src/translator/terminator/mod.rs +++ b/cuda-oxide/crates/mir-importer/src/translator/terminator/mod.rs @@ -2881,6 +2881,25 @@ fn try_dispatch_intrinsic( )?)); } + if let Some(intrinsic) = intrinsics::float_to_int::RustFloatToIntIntrinsic::from_core_path(name) + { + return Ok(Some( + intrinsics::float_to_int::emit_rust_float_to_int_intrinsic( + ctx, + body, + intrinsic, + args, + destination, + target, + block_ptr, + prev_op, + value_map, + block_map, + loc, + )?, + )); + } + if let Some(intrinsic) = intrinsics::bigint::RustBigIntIntrinsic::from_core_path(name) { return Ok(Some(intrinsics::bigint::emit_rust_bigint_intrinsic( ctx, diff --git a/cuda-oxide/crates/mir-lower/src/convert/ops/call.rs b/cuda-oxide/crates/mir-lower/src/convert/ops/call.rs index 20b319759a..0150492533 100644 --- a/cuda-oxide/crates/mir-lower/src/convert/ops/call.rs +++ b/cuda-oxide/crates/mir-lower/src/convert/ops/call.rs @@ -81,7 +81,7 @@ use llvm_export::types::PointerTypeExt; use pliron::builtin::attributes::IntegerAttr; use pliron::builtin::op_interfaces::{CallOpCallable, SymbolOpInterface}; use pliron::builtin::type_interfaces::FunctionTypeInterface; -use pliron::builtin::types::{IntegerType, Signedness}; +use pliron::builtin::types::{FP32Type, FP64Type, IntegerType, Signedness}; use pliron::context::{Context, Ptr}; use pliron::irbuild::dialect_conversion::{DialectConversionRewriter, OperandsInfo}; use pliron::irbuild::inserter::Inserter; @@ -167,6 +167,21 @@ impl RustExactDivIntrinsic { } } +/// Internal placeholder for `core::intrinsics::float_to_int_unchecked`. +/// +/// The caller has promised the value is finite and fits the destination type +/// once truncated, so this lowers to a plain `fptosi`/`fptoui` with none of +/// the clamping of the saturating `as` cast. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct RustFloatToIntUncheckedIntrinsic; + +impl RustFloatToIntUncheckedIntrinsic { + /// Convert an importer placeholder name back into the intrinsic it represents. + fn from_placeholder_callee(callee: &str) -> Option { + (callee == rust_intrinsics::CALLEE_FLOAT_TO_INT_UNCHECKED).then_some(Self) + } +} + /// Internal placeholder for rustc bigint helper intrinsics. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum RustBigIntIntrinsic { @@ -233,6 +248,10 @@ pub fn convert( return convert_rust_exact_div(ctx, rewriter, op, operands_info); } + if RustFloatToIntUncheckedIntrinsic::from_placeholder_callee(&callee_name).is_some() { + return convert_rust_float_to_int_unchecked(ctx, rewriter, op); + } + if let Some(RustBigIntIntrinsic::CarryingMulAdd) = RustBigIntIntrinsic::from_placeholder_callee(&callee_name) { @@ -717,6 +736,73 @@ fn convert_rust_exact_div( Ok(()) } +/// Convert `core::intrinsics::float_to_int_unchecked` to `llvm.fptosi` or +/// `llvm.fptoui`. +/// +/// The intrinsic's contract is that the value is not NaN or infinite and fits +/// the destination type once truncated toward zero; violating it is undefined +/// behaviour. That is exactly the contract of a bare LLVM `fptosi`/`fptoui`, +/// so none of the clamping or NaN handling of the saturating `as` cast +/// (`llvm.fptosi.sat`/`llvm.fptoui.sat`, see `cast.rs`) is emitted. +/// +/// Signedness comes from the MIR result type, the same source the saturating +/// cast uses, because the LLVM integer type is signless. Sources and +/// destinations are limited to what that cast supports on every backend: +/// `f32`/`f64` into integers of at most 64 bits. +fn convert_rust_float_to_int_unchecked( + ctx: &mut Context, + rewriter: &mut DialectConversionRewriter, + op: Ptr, +) -> Result<()> { + let loc = op.deref(ctx).loc().clone(); + if op.deref(ctx).get_num_results() != 1 { + return pliron::input_err!(loc, "float_to_int_unchecked call must have one result"); + } + + let args: Vec = op.deref(ctx).operands().collect(); + let [value] = args[..] else { + return pliron::input_err!(loc, "float_to_int_unchecked requires one operand"); + }; + + let is_supported_source = { + let source_ty = value.get_type(ctx); + let source_ref = source_ty.deref(ctx); + source_ref.is::() || source_ref.is::() + }; + if !is_supported_source { + return pliron::input_err!( + loc, + "float_to_int_unchecked: only f32 and f64 sources are supported on the device" + ); + } + + let result_mir_ty = op.deref(ctx).get_result(0).get_type(ctx); + let (is_signed, width) = { + let result_ref = result_mir_ty.deref(ctx); + let Some(int_ty) = result_ref.downcast_ref::() else { + return pliron::input_err!(loc, "float_to_int_unchecked requires an integer result"); + }; + // Signless lowers as unsigned, matching the saturating cast. + (int_ty.signedness() == Signedness::Signed, int_ty.width()) + }; + if width > 64 { + return pliron::input_err!( + loc, + "float_to_int_unchecked: integer destinations wider than 64 bits are not yet supported on the device" + ); + } + + let result_ty = convert_type(ctx, result_mir_ty).map_err(anyhow_to_pliron)?; + let llvm_op = if is_signed { + llvm::FPToSIOp::new(ctx, value, result_ty).get_operation() + } else { + llvm::FPToUIOp::new(ctx, value, result_ty).get_operation() + }; + rewriter.insert_operation(ctx, llvm_op); + rewriter.replace_operation(ctx, op, llvm_op); + Ok(()) +} + /// Lower the placeholder call for rustc's `carrying_mul_add` bigint intrinsic. /// /// `core::intrinsics::carrying_mul_add(a, b, c, d)` computes `a * b + c + d` @@ -1477,4 +1563,28 @@ mod tests { ); } } + + /// The `float_to_int_unchecked` placeholder is recognized, and nothing + /// else is. + #[test] + fn test_float_to_int_unchecked_placeholder_round_trip() { + assert!( + RustFloatToIntUncheckedIntrinsic::from_placeholder_callee( + rust_intrinsics::CALLEE_FLOAT_TO_INT_UNCHECKED + ) + .is_some() + ); + + for other in [ + rust_intrinsics::CALLEE_EXACT_DIV, + rust_intrinsics::CALLEE_SATURATING_ADD, + rust_intrinsics::CALLEE_TRUNC_F32, + "core::intrinsics::float_to_int_unchecked", + ] { + assert!( + RustFloatToIntUncheckedIntrinsic::from_placeholder_callee(other).is_none(), + "`{other}` must not be treated as float_to_int_unchecked" + ); + } + } } diff --git a/cuda-oxide/crates/mir-lower/tests/lowering_test/math_conversions.rs b/cuda-oxide/crates/mir-lower/tests/lowering_test/math_conversions.rs index a0b58e1ad4..478b0f836f 100644 --- a/cuda-oxide/crates/mir-lower/tests/lowering_test/math_conversions.rs +++ b/cuda-oxide/crates/mir-lower/tests/lowering_test/math_conversions.rs @@ -3038,3 +3038,119 @@ fn test_scalar_arithmetic_libnvvm_uses_exact_inline_ptx() -> Result<(), anyhow:: ); Ok(()) } + +/// Append a `float_to_int_unchecked` placeholder call to `block`. +fn append_float_to_int_unchecked_call( + ctx: &mut Context, + block: pliron::context::Ptr, + value: pliron::value::Value, + result_ty: pliron::r#type::TypeHandle, +) { + use pliron::builtin::attributes::StringAttr; + + let call_ptr = Operation::new( + ctx, + mir::MirCallOp::get_concrete_op_info(), + vec![result_ty], + vec![value], + vec![], + 0, + ); + mir::MirCallOp::new(call_ptr).set_attr_callee( + ctx, + StringAttr::new(dialect_mir::rust_intrinsics::CALLEE_FLOAT_TO_INT_UNCHECKED.to_string()), + ); + call_ptr.insert_at_back(block, ctx); +} + +/// `float_to_int_unchecked` lowers to a bare `fptosi`/`fptoui`, picked from +/// the destination's signedness, on both intrinsic backends. No saturating +/// `llvm.fpto*i.sat` call (the `as` cast lowering) and no placeholder call +/// may survive. +#[test] +fn test_float_to_int_unchecked_lowers_to_plain_fptosi_fptoui() -> Result<(), anyhow::Error> { + use pliron::builtin::types::{FP32Type, FP64Type, IntegerType, Signedness}; + use pliron::r#type::{TypeHandle, Typed}; + + for backend in [ + mir_lower::IntrinsicBackend::LlvmNvptx, + mir_lower::IntrinsicBackend::LibNvvm, + ] { + let mut ctx = make_test_ctx(); + let f32_ty: TypeHandle = FP32Type::get(&ctx).into(); + let f64_ty: TypeHandle = FP64Type::get(&ctx).into(); + let (module_ptr, entry) = build_test_kernel(&mut ctx, vec![f32_ty, f64_ty]); + let f32_value = entry.deref(&ctx).get_argument(0); + let f64_value = entry.deref(&ctx).get_argument(1); + + for (value, width, signedness) in [ + (f32_value, 32, Signedness::Signed), + (f32_value, 32, Signedness::Unsigned), + (f64_value, 64, Signedness::Signed), + (f64_value, 64, Signedness::Unsigned), + ] { + let result_ty: TypeHandle = IntegerType::get(&ctx, width, signedness).into(); + append_float_to_int_unchecked_call(&mut ctx, entry, value, result_ty); + } + append_return(&mut ctx, entry); + + mir_lower::lower_mir_to_llvm_with_options( + &mut ctx, + module_ptr, + mir_lower::LoweringOptions { + intrinsic_backend: backend, + ..Default::default() + }, + ) + .map_err(|e| anyhow::anyhow!("{}", e))?; + + let result_width = |op: pliron::context::Ptr| { + let ty = op.deref(&ctx).get_result(0).get_type(&ctx); + ty.deref(&ctx) + .downcast_ref::() + .map(|int_ty| int_ty.width()) + }; + let mut signed_widths = Vec::new(); + let mut unsigned_widths = Vec::new(); + for op in lowered_kernel_body(&ctx, module_ptr) { + assert!( + Operation::get_op::(op, &ctx).is_none(), + "float_to_int_unchecked placeholder mir.call must not survive MIR lowering" + ); + assert!( + Operation::get_op::(op, &ctx).is_none(), + "float_to_int_unchecked must not lower to a call (saturating or placeholder)" + ); + if Operation::get_op::(op, &ctx).is_some() { + signed_widths.push(result_width(op)); + } + if Operation::get_op::(op, &ctx).is_some() { + unsigned_widths.push(result_width(op)); + } + } + assert_eq!(signed_widths, [Some(32), Some(64)], "{backend:?}"); + assert_eq!(unsigned_widths, [Some(32), Some(64)], "{backend:?}"); + } + Ok(()) +} + +/// Destinations wider than 64 bits fail with a clear error, as the +/// saturating `as` cast does, rather than reaching the backend. +#[test] +fn test_float_to_int_unchecked_rejects_wide_destination() { + use pliron::builtin::types::{FP32Type, IntegerType, Signedness}; + use pliron::r#type::TypeHandle; + + let mut ctx = make_test_ctx(); + let f32_ty: TypeHandle = FP32Type::get(&ctx).into(); + let (module_ptr, entry) = build_test_kernel(&mut ctx, vec![f32_ty]); + let value = entry.deref(&ctx).get_argument(0); + let i128_ty: TypeHandle = IntegerType::get(&ctx, 128, Signedness::Signed).into(); + append_float_to_int_unchecked_call(&mut ctx, entry, value, i128_ty); + append_return(&mut ctx, entry); + + let error = mir_lower::lower_mir_to_llvm(&mut ctx, module_ptr) + .expect_err("a 128-bit float_to_int_unchecked destination must not lower") + .to_string(); + assert!(error.contains("wider than 64 bits"), "{error}"); +} diff --git a/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/Cargo.lock b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/Cargo.lock new file mode 100644 index 0000000000..5236dd08a4 --- /dev/null +++ b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/Cargo.lock @@ -0,0 +1,754 @@ +# 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 = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[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 = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +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 = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cuda-artifact-finalizer" +version = "0.2.1" +dependencies = [ + "libnvvm-sys", + "nvjitlink-sys", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "cuda-bindings" +version = "0.5.0" +dependencies = [ + "bindgen", + "libloading", + "prettyplease", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cuda-core" +version = "0.5.0" +dependencies = [ + "anyhow", + "cuda-bindings", + "cuda-core-derive", + "half", + "oxide-artifacts", +] + +[[package]] +name = "cuda-core-derive" +version = "0.5.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cuda-device" +version = "0.2.1" +dependencies = [ + "cuda-macros", +] + +[[package]] +name = "cuda-host" +version = "0.2.1" +dependencies = [ + "cuda-artifact-finalizer", + "cuda-core", + "cuda-device", + "cuda-macros", + "half", + "libc", + "oxide-artifacts", + "ptx-parse", + "sha2", + "thiserror", +] + +[[package]] +name = "cuda-macros" +version = "0.2.1" +dependencies = [ + "oxide-tile-export", + "proc-macro2", + "quote", + "reserved-oxide-symbols", + "syn", +] + +[[package]] +name = "cuda-target-spec" +version = "0.1.0" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[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 = "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 = "float_to_int_unchecked" +version = "0.1.0" +dependencies = [ + "cuda-core", + "cuda-device", + "cuda-host", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[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 = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[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 = [ + "cuda-target-spec", + "libloading", + "thiserror", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[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 = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oxide-artifacts" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d9a941c0c43d2dceaf25f5c9ca4c9f9cceb28dfa5e4e3cfd14e476f003e7c9" +dependencies = [ + "object", +] + +[[package]] +name = "oxide-tile-export" +version = "0.2.1" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + +[[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 = "ptx-parse" +version = "0.1.0" + +[[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.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.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 = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[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.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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 = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[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", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/Cargo.toml b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/Cargo.toml new file mode 100644 index 0000000000..3faffdd53e --- /dev/null +++ b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "float_to_int_unchecked" +version = "0.1.0" +edition = "2024" +authors = ["Daniel Veljjanoski "] +license = "Apache-2.0" + +# Mark as standalone crate (not part of parent workspace) +[workspace] + +# Exercises f32/f64::to_int_unchecked, which calls +# core::intrinsics::float_to_int_unchecked. +# Build with: +# cargo oxide run float_to_int_unchecked --arch sm_86 + +[dependencies] +# Device-side intrinsics (thread indexing, barriers, etc.) +cuda-device = { path = "../../../cuda-device" } + +# Host-side utilities (CudaKernel trait, etc.) +cuda-host = { path = "../../../cuda-host" } + +# Host-side CUDA runtime +cuda-core = { path = "../../../../../cuda-core" } diff --git a/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/README.md b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/README.md new file mode 100644 index 0000000000..daf48bb4b6 --- /dev/null +++ b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/README.md @@ -0,0 +1,52 @@ +# float_to_int_unchecked + +Exercises `f32::to_int_unchecked` and `f64::to_int_unchecked`, which call +`core::intrinsics::float_to_int_unchecked`. + +Before the intrinsic was implemented, a kernel using either method failed to +translate (#1595): + +``` +Translation failed: f64_to_i64: [".../core/src/convert/num.rs": line: 20, column: 30] + Compilation error: invalid input program. + Unsupported construct: rustc intrinsic `std::intrinsics::float_to_int_unchecked` + is not yet supported on the device +``` + +That line is the `float_to_int_unchecked(self)` call inside `to_int_unchecked`. + +## Run + +```bash +cargo oxide run float_to_int_unchecked --arch sm_86 +``` + +Expected output: + +``` +f32 -> i32: 256 / 256 correct +f32 -> u32: 256 / 256 correct +f64 -> i64: 256 / 256 correct +f64 -> u64: 256 / 256 correct + +PASS +``` + +## What it checks + +The intrinsic lowers to a plain `fptosi` or `fptoui`, picked from the +destination's signedness, with none of the clamping and NaN handling that the +saturating `as` cast needs. Each kernel feeds values that only the right +instruction converts correctly: + +| Kernel | Inputs | +|--------------|----------------------------------------------| +| `f32_to_i32` | negative and positive, with fractional parts | +| `f32_to_u32` | up to about 4.08e9, above `i32::MAX` | +| `f64_to_i64` | -8.96e18 to 8.89e18, outside the i32 range | +| `f64_to_u64` | up to about 1.79e19, above `i64::MAX` | + +The host's `as` cast is the reference. For in-range inputs it gives the same +value as `to_int_unchecked`; they differ only where `to_int_unchecked` is +undefined behaviour. Every input is finite and in range for its destination, +as the method's contract requires. diff --git a/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/src/main.rs b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/src/main.rs new file mode 100644 index 0000000000..565cef6b63 --- /dev/null +++ b/cuda-oxide/crates/rustc-codegen-cuda/examples/float_to_int_unchecked/src/main.rs @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! End-to-end check that `f32::to_int_unchecked` / `f64::to_int_unchecked` +//! work in device code. +//! +//! Both methods call `core::intrinsics::float_to_int_unchecked`. Before that +//! intrinsic was supported, a kernel using either failed to translate (#1595): +//! +//! ```text +//! Translation failed: f64_to_i64: [".../core/src/convert/num.rs": line: 20, column: 30] +//! Compilation error: invalid input program. +//! Unsupported construct: rustc intrinsic `std::intrinsics::float_to_int_unchecked` +//! is not yet supported on the device +//! ``` +//! +//! The intrinsic lowers to a plain `fptosi` / `fptoui`, picked from the +//! destination's signedness. Each kernel below feeds values only one of the +//! two instructions handles: negative values for the signed arms, and values +//! above the signed maximum for the unsigned arms. Every input is finite and +//! in range for its destination type, as the method's contract requires. +//! +//! Run: `cargo oxide run float_to_int_unchecked --arch sm_86` + +use cuda_core::simt::LaunchConfig; +use cuda_core::{CudaContext, DeviceBuffer}; +use cuda_device::{DisjointSlice, cuda_module, kernel, thread}; + +const N: usize = 256; + +#[cuda_module] +mod kernels { + use super::*; + + /// `f32 -> i32`, the case reported in #1595. + #[kernel] + pub fn f32_to_i32(x: &[f32], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if let Some(o) = out.get_mut(idx) { + // SAFETY: the host inputs are finite and inside the i32 range. + *o = unsafe { x[i].to_int_unchecked::() }; + } + } + + /// `f32 -> u32`, with inputs above `i32::MAX`. + #[kernel] + pub fn f32_to_u32(x: &[f32], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if let Some(o) = out.get_mut(idx) { + // SAFETY: the host inputs are finite and inside the u32 range. + *o = unsafe { x[i].to_int_unchecked::() }; + } + } + + /// `f64 -> i64`, with inputs outside the i32 range. + #[kernel] + pub fn f64_to_i64(x: &[f64], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if let Some(o) = out.get_mut(idx) { + // SAFETY: the host inputs are finite and inside the i64 range. + *o = unsafe { x[i].to_int_unchecked::() }; + } + } + + /// `f64 -> u64`, with inputs above `i64::MAX`. + #[kernel] + pub fn f64_to_u64(x: &[f64], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if let Some(o) = out.get_mut(idx) { + // SAFETY: the host inputs are finite and inside the u64 range. + *o = unsafe { x[i].to_int_unchecked::() }; + } + } +} + +/// Count mismatches against the host conversion and print the first few. +fn check(label: &str, got: &[T], want: &[T]) -> usize { + let mut bad = 0; + for (i, (g, w)) in got.iter().zip(want).enumerate() { + if g != w { + if bad < 5 { + println!(" {label} mismatch at {i}: got {g} want {w}"); + } + bad += 1; + } + } + println!("{label}: {} / {N} correct", N - bad); + bad +} + +fn main() { + let ctx = CudaContext::new(0).expect("CUDA context"); + let stream = ctx.default_stream(); + let module = kernels::load(&ctx).expect("load embedded module"); + let cfg = LaunchConfig::for_num_elems(N as u32); + + // Negative and positive values with fractional parts, so truncation + // toward zero (-12345.678 -> -12345) is checked against rounding down. + let x_i32: Vec = (0..N).map(|i| (i as f32 - 128.0) * 12345.678).collect(); + // Up to ~4.08e9: above i32::MAX, below u32::MAX. + let x_u32: Vec = (0..N).map(|i| i as f32 * 1.6e7 + 0.5).collect(); + // -8.96e18 to 8.89e18: outside the i32 range, inside the i64 range. + let x_i64: Vec = (0..N).map(|i| (i as f64 - 128.0) * 7.0e16 + 0.5).collect(); + // Up to ~1.79e19: above i64::MAX, below u64::MAX. + let x_u64: Vec = (0..N).map(|i| i as f64 * 7.0e16 + 0.25).collect(); + + // The host's `as` cast gives the same value as `to_int_unchecked` for + // in-range inputs; it differs only where `to_int_unchecked` is undefined. + let want_i32: Vec = x_i32.iter().map(|&v| v as i32).collect(); + let want_u32: Vec = x_u32.iter().map(|&v| v as u32).collect(); + let want_i64: Vec = x_i64.iter().map(|&v| v as i64).collect(); + let want_u64: Vec = x_u64.iter().map(|&v| v as u64).collect(); + + let in_i32 = DeviceBuffer::from_host(&stream, &x_i32).unwrap(); + let in_u32 = DeviceBuffer::from_host(&stream, &x_u32).unwrap(); + let in_i64 = DeviceBuffer::from_host(&stream, &x_i64).unwrap(); + let in_u64 = DeviceBuffer::from_host(&stream, &x_u64).unwrap(); + let mut out_i32 = DeviceBuffer::::zeroed(&stream, N).unwrap(); + let mut out_u32 = DeviceBuffer::::zeroed(&stream, N).unwrap(); + let mut out_i64 = DeviceBuffer::::zeroed(&stream, N).unwrap(); + let mut out_u64 = DeviceBuffer::::zeroed(&stream, N).unwrap(); + + // SAFETY: each launch is 1-D with one thread per element, matching + // `index_1d()`, and every buffer holds N elements. + unsafe { + module + .f32_to_i32(&stream, cfg, &in_i32, &mut out_i32) + .expect("f32_to_i32 launch"); + module + .f32_to_u32(&stream, cfg, &in_u32, &mut out_u32) + .expect("f32_to_u32 launch"); + module + .f64_to_i64(&stream, cfg, &in_i64, &mut out_i64) + .expect("f64_to_i64 launch"); + module + .f64_to_u64(&stream, cfg, &in_u64, &mut out_u64) + .expect("f64_to_u64 launch"); + } + + let got_i32 = out_i32.to_host_vec(&stream).unwrap(); + let got_u32 = out_u32.to_host_vec(&stream).unwrap(); + let got_i64 = out_i64.to_host_vec(&stream).unwrap(); + let got_u64 = out_u64.to_host_vec(&stream).unwrap(); + let bad = check("f32 -> i32", &got_i32, &want_i32) + + check("f32 -> u32", &got_u32, &want_u32) + + check("f64 -> i64", &got_i64, &want_i64) + + check("f64 -> u64", &got_u64, &want_u64); + + if bad == 0 { + println!("\nPASS"); + } else { + println!("\nFAIL"); + std::process::exit(1); + } +}