Skip to content

WarpMatchValue for f32/f64 uses truncating casts instead of to_bits() #406

Description

@Snehal-Reddy

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:

  1. 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.
  2. 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
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions