f32 and f64 types are mistakenly implemented using the generic impl_match! macro which uses the as keyword to cast values to unsigned integers, rather than properly preserving their bit representation for PTX comparison.
This presents the following issues:
- Code that uses
warp_match_any on f32 or f64 intends to check if threads in a warp possess the exact same floating-point value. However, the as u32 cast on an f32 in Rust executes a truncating numeric conversion. For example, 1.1f32 and 1.9f32 are both cast to 1u32.
- Because different fractional values (and all negative floats, which inherently cast to
0) produce identical integer masks under this truncation, the underlying PTX intrinsic will incorrectly report that these threads share matching values. Algorithms relying on this for SIMT execution branch convergence or duplicate filtering will proceed with corrupted state without emitting any compilation errors or runtime warnings.
Reproduction Case
Using warp_match_any on distinct f32 or f64 floating-point numbers will erroneously return that the threads have matching values natively:
use cuda_std::warp::warp_match_any;
#[cuda_std::kernel]
pub unsafe fn match_float_kernel() {
use cuda_std::thread;
let tid = thread::thread_idx_x();
// Thread 0 has 1.1, Thread 1 has 1.9, Thread 2 has -5.5
let val: f32 = if tid == 0 { 1.1 } else if tid == 1 { 1.9 } else { -5.5 };
// PTX generates match.any on the integer cast of val.
// 1.1 as u32 == 1, 1.9 as u32 == 1, and -5.5 as u32 == 0!
// This incorrectly evaluates Threads 0 and 1 as a perfect match.
let matching_mask = unsafe { warp_match_any(0xFFFFFFFF, val) };
}
Issue Details
In crates/cuda_std/src/warp.rs, the type instantiations for WarpMatchValue generically apply value as [<u $width>] inside the impl_match! macro. While this successfully behaves as a bitwise cast for integers like i32 and i64, it is invalid for floats which must explicitly use to_bits():
// Current macro definition in warp.rs
macro_rules! impl_match {
($($type:ty, $width:literal),* $(,)?) => {
$(
paste::paste! {
impl WarpMatchValue for $type {
unsafe fn match_any(mask: u32, value: Self) -> u32 {
// <--- BUG: `f32 as u32` evaluates as truncating numeric conversion (`1.9 -> 1`)
unsafe { [<match_any_ $width>](mask, value as [<u $width>]) }
}
// ...
}
}
)*
}
}
// Current instantiations using the macro
impl_match! {
i32, 32,
i64, 64,
u32, 32,
u64, 64,
f32, 32, // <--- BUG: Floats should not be dynamically handled via integer `as` casting
f64, 64, // <--- BUG: Floats should not be dynamically handled via integer `as` casting
}
f32andf64types are mistakenly implemented using the genericimpl_match!macro which uses theaskeyword to cast values to unsigned integers, rather than properly preserving their bit representation for PTX comparison.This presents the following issues:
warp_match_anyonf32orf64intends to check if threads in a warp possess the exact same floating-point value. However, theas u32cast on anf32in Rust executes a truncating numeric conversion. For example,1.1f32and1.9f32are both cast to1u32.0) produce identical integer masks under this truncation, the underlying PTX intrinsic will incorrectly report that these threads share matching values. Algorithms relying on this for SIMT execution branch convergence or duplicate filtering will proceed with corrupted state without emitting any compilation errors or runtime warnings.Reproduction Case
Using
warp_match_anyon distinctf32orf64floating-point numbers will erroneously return that the threads have matching values natively:Issue Details
In
crates/cuda_std/src/warp.rs, the type instantiations forWarpMatchValuegenerically applyvalue as [<u $width>]inside theimpl_match!macro. While this successfully behaves as a bitwise cast for integers likei32andi64, it is invalid for floats which must explicitly useto_bits():