rust-lang / portable-simd

The testing ground for the future of portable SIMD in Rust
Apache License 2.0
903 stars 81 forks source link

How to `Simd::cast` now that it is removed #358

Closed ritchie46 closed 1 year ago

ritchie46 commented 1 year ago

350 removed Simd::cast<U>.

This broke our code upstream and I fail to understand how I now can coerce a Simd<T, N> to Simd<f64, N>?

Is there a cast function/trait I missed?

calebzulawski commented 1 year ago

Are you using the git repo, instead of std::simd?

The cast functions are now part of the element traits, e.g. SimdInt::cast.

ritchie46 commented 1 year ago

Are you using the git repo, instead of std::simd?

No, I pulled in version nightly-2023-07-27.

So we have this function that is generic over T and is called with f32, f64 and all integer types. Is there a trait that combines cast for all those types?

fn nonnull_sum_as_f64<T>(values: &[T]) -> f64
where
    T: NativeType + SimdElement + ToPrimitive + SimdCast,
{
    // we choose 8 as that the maximum size of f64x8 -> 512bit wide
    const LANES: usize = 8;
    let (head, simd_vals, tail) = unsafe { values.align_to::<Simd<T, LANES>>() };

    let mut reduced: Simd<f64, LANES> = Simd::splat(0.0);
    for chunk in simd_vals {
        reduced += chunk.cast::<f64>();
    }

    unsafe {
        reduced.reduce_sum()
            + head
                .iter()
                .map(|v| v.to_f64().unwrap_unchecked())
                .sum::<f64>()
            + tail
                .iter()
                .map(|v| v.to_f64().unwrap_unchecked())
                .sum::<f64>()
    }
}
calebzulawski commented 1 year ago

Ah, now I understand. There is no longer any generic cast that works for any element type--it's tied to the element. This is because some elements have their own cast semantics (e.g. pointers) and in the future, some element types may not support casts at all.

You may want to implement your own trait to handle this. In the future I expect a community crate to help (I am working on one, but it is not yet published)

ritchie46 commented 1 year ago

You may want to implement your own trait to handle this.

Right, that's what I did in the meantime:

pub trait SimdCastPl<const N: usize>
where
    LaneCount<N>: SupportedLaneCount,
{
    fn cast_custom<U: SimdCast>(self) -> Simd<U, N>;
}

macro_rules! impl_cast_custom {
    ($_type:ty) => {
        impl<const N: usize> SimdCastPl<N> for Simd<$_type, N>
        where
            LaneCount<N>: SupportedLaneCount,
        {
            fn cast_custom<U: SimdCast>(self) -> Simd<U, N> {
                self.cast::<U>()
            }
        }
    };
}

impl_cast_custom!(u8);
impl_cast_custom!(u16);
impl_cast_custom!(u32);
impl_cast_custom!(u64);
impl_cast_custom!(i8);
impl_cast_custom!(i16);
impl_cast_custom!(i32);
impl_cast_custom!(i64);
impl_cast_custom!(f32);
impl_cast_custom!(f64);

some element types may not support casts at all.

primitives to primitives remain supported? Or is it all in the open?

calebzulawski commented 1 year ago

That's pretty much how I would do it.

some element types may not support casts at all.

primitives to primitives remain supported? Or is it all in the open?

The existing casts will continue to exist, I meant if we add new supported element types that are more exotic than primitives.

ritchie46 commented 1 year ago

Check. Thanks for the help! :slightly_smiling_face: