Skip to content

Commit 3692383

Browse files
committed
Add support for masked loads & stores
1 parent fbc9efa commit 3692383

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

crates/core_simd/src/vector.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,131 @@ where
314314
unsafe { self.store(slice.as_mut_ptr().cast()) }
315315
}
316316

317+
/// Reads contiguous elements from `slice`. Elements are read so long as they're in-bounds for
318+
/// the `slice`. Otherwise, the default value for the element type is returned.
319+
///
320+
/// # Examples
321+
/// ```
322+
/// # #![feature(portable_simd)]
323+
/// # use core::simd::{Simd, Mask};
324+
/// let vec: Vec<i32> = vec![10, 11];
325+
///
326+
/// let result = Simd::<i32, 4>::load_or_default(&vec);
327+
/// assert_eq!(result, Simd::from_array([10, 11, 0, 0]));
328+
/// ```
329+
#[must_use]
330+
#[inline]
331+
pub fn load_or_default(slice: &[T]) -> Self
332+
where
333+
T: Default,
334+
{
335+
Self::load_or(slice, Default::default())
336+
}
337+
338+
/// Reads contiguous elements from `slice`. Elements are read so long as they're in-bounds for
339+
/// the `slice`. Otherwise, the corresponding value from `or` is passed through.
340+
///
341+
/// # Examples
342+
/// ```
343+
/// # #![feature(portable_simd)]
344+
/// # use core::simd::{Simd, Mask};
345+
/// let vec: Vec<i32> = vec![10, 11];
346+
/// let or = Simd::from_array([-5, -4, -3, -2]);
347+
///
348+
/// let result = Simd::load_or(&vec, or);
349+
/// assert_eq!(result, Simd::from_array([10, 11, -3, -2]));
350+
/// ```
351+
#[must_use]
352+
#[inline]
353+
pub fn load_or(slice: &[T], or: Self) -> Self {
354+
Self::load_select(slice, Mask::splat(true), or)
355+
}
356+
357+
/// Reads contiguous elements from `slice`. Each lane is read from memory if its
358+
/// corresponding lane in `enable` is `true`.
359+
///
360+
/// When the lane is disabled or out of bounds for the slice, that memory location
361+
/// is not accessed and the corresponding value from `or` is passed through.
362+
///
363+
/// # Examples
364+
/// ```
365+
/// # #![feature(portable_simd)]
366+
/// # use core::simd::{Simd, Mask};
367+
/// let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18];
368+
/// let enable = Mask::from_array([true, true, false, true]);
369+
/// let or = Simd::from_array([-5, -4, -3, -2]);
370+
///
371+
/// let result = Simd::load_select(&vec, enable, or);
372+
/// assert_eq!(result, Simd::from_array([10, 11, -3, 14]));
373+
/// ```
374+
#[must_use]
375+
#[inline]
376+
pub fn load_select_or_default(slice: &[T], enable: Mask<<T as SimdElement>::Mask, N>) -> Self
377+
where
378+
T: Default,
379+
{
380+
Self::load_select(slice, enable, Default::default())
381+
}
382+
383+
/// Reads contiguous elements from `slice`. Each lane is read from memory if its
384+
/// corresponding lane in `enable` is `true`.
385+
///
386+
/// When the lane is disabled or out of bounds for the slice, that memory location
387+
/// is not accessed and the corresponding value from `or` is passed through.
388+
///
389+
/// # Examples
390+
/// ```
391+
/// # #![feature(portable_simd)]
392+
/// # use core::simd::{Simd, Mask};
393+
/// let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18];
394+
/// let enable = Mask::from_array([true, true, false, true]);
395+
/// let or = Simd::from_array([-5, -4, -3, -2]);
396+
///
397+
/// let result = Simd::load_select(&vec, enable, or);
398+
/// assert_eq!(result, Simd::from_array([10, 11, -3, 14]));
399+
/// ```
400+
#[must_use]
401+
#[inline]
402+
pub fn load_select(
403+
slice: &[T],
404+
mut enable: Mask<<T as SimdElement>::Mask, N>,
405+
or: Self,
406+
) -> Self {
407+
enable &= mask_up_to(enable, slice.len());
408+
unsafe { Self::load_select_ptr(slice.as_ptr(), enable, or) }
409+
}
410+
411+
/// Reads contiguous elements from `slice`. Each lane is read from memory if its
412+
/// corresponding lane in `enable` is `true`.
413+
///
414+
/// When the lane is disabled, that memory location is not accessed and the corresponding
415+
/// value from `or` is passed through.
416+
#[must_use]
417+
#[inline]
418+
pub unsafe fn load_select_unchecked(
419+
slice: &[T],
420+
enable: Mask<<T as SimdElement>::Mask, N>,
421+
or: Self,
422+
) -> Self {
423+
let ptr = slice.as_ptr();
424+
unsafe { Self::load_select_ptr(ptr, enable, or) }
425+
}
426+
427+
/// Reads contiguous elements starting at `ptr`. Each lane is read from memory if its
428+
/// corresponding lane in `enable` is `true`.
429+
///
430+
/// When the lane is disabled, that memory location is not accessed and the corresponding
431+
/// value from `or` is passed through.
432+
#[must_use]
433+
#[inline]
434+
pub unsafe fn load_select_ptr(
435+
ptr: *const T,
436+
enable: Mask<<T as SimdElement>::Mask, N>,
437+
or: Self,
438+
) -> Self {
439+
unsafe { core::intrinsics::simd::simd_masked_load(enable.to_int(), ptr, or) }
440+
}
441+
317442
/// Reads from potentially discontiguous indices in `slice` to construct a SIMD vector.
318443
/// If an index is out-of-bounds, the element is instead selected from the `or` vector.
319444
///
@@ -492,6 +617,27 @@ where
492617
unsafe { core::intrinsics::simd::simd_gather(or, source, enable.to_int()) }
493618
}
494619

620+
#[inline]
621+
pub fn masked_store(self, slice: &mut [T], mut enable: Mask<<T as SimdElement>::Mask, N>) {
622+
enable &= mask_up_to(enable, slice.len());
623+
unsafe { self.masked_store_ptr(slice.as_mut_ptr(), enable) }
624+
}
625+
626+
#[inline]
627+
pub unsafe fn masked_store_unchecked(
628+
self,
629+
slice: &mut [T],
630+
enable: Mask<<T as SimdElement>::Mask, N>,
631+
) {
632+
let ptr = slice.as_mut_ptr();
633+
unsafe { self.masked_store_ptr(ptr, enable) }
634+
}
635+
636+
#[inline]
637+
pub unsafe fn masked_store_ptr(self, ptr: *mut T, enable: Mask<<T as SimdElement>::Mask, N>) {
638+
unsafe { core::intrinsics::simd::simd_masked_store(enable.to_int(), ptr, self) }
639+
}
640+
495641
/// Writes the values in a SIMD vector to potentially discontiguous indices in `slice`.
496642
/// If an index is out-of-bounds, the write is suppressed without panicking.
497643
/// If two elements in the scattered vector would write to the same index
@@ -979,3 +1125,27 @@ where
9791125
{
9801126
type Mask = isize;
9811127
}
1128+
1129+
#[inline]
1130+
fn lane_indices<T, const N: usize>() -> Simd<T, N>
1131+
where
1132+
T: MaskElement + Default + core::convert::From<i8> + core::ops::Add<T, Output = T>,
1133+
LaneCount<N>: SupportedLaneCount,
1134+
{
1135+
let mut index = [T::default(); N];
1136+
for i in 1..N {
1137+
index[i] = index[i - 1] + T::from(1);
1138+
}
1139+
Simd::from_array(index)
1140+
}
1141+
1142+
#[inline]
1143+
fn mask_up_to<M, const N: usize>(enable: Mask<M, N>, len: usize) -> Mask<M, N>
1144+
where
1145+
LaneCount<N>: SupportedLaneCount,
1146+
M: MaskElement,
1147+
{
1148+
let index = lane_indices::<i8, N>();
1149+
let lt = index.simd_lt(Simd::splat(i8::try_from(len).unwrap_or(i8::MAX)));
1150+
enable & Mask::<M, N>::from_bitmask_vector(lt.to_bitmask_vector())
1151+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![feature(portable_simd)]
2+
use core_simd::simd::prelude::*;
3+
4+
#[cfg(target_arch = "wasm32")]
5+
use wasm_bindgen_test::*;
6+
7+
#[cfg(target_arch = "wasm32")]
8+
wasm_bindgen_test_configure!(run_in_browser);
9+
10+
#[test]
11+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
12+
fn masked_load_store() {
13+
let mut arr = [u8::MAX; 7];
14+
15+
u8x4::splat(0).masked_store(&mut arr[5..], Mask::from_array([false, true, false, true]));
16+
// write to index 8 is OOB and dropped
17+
assert_eq!(arr, [255u8, 255, 255, 255, 255, 255, 0]);
18+
19+
u8x4::from_array([0, 1, 2, 3]).masked_store(&mut arr[1..], Mask::splat(true));
20+
assert_eq!(arr, [255u8, 0, 1, 2, 3, 255, 0]);
21+
22+
// read from index 8 is OOB and dropped
23+
assert_eq!(
24+
u8x4::load_or(&arr[4..], u8x4::splat(42)),
25+
u8x4::from_array([3, 255, 0, 42])
26+
);
27+
assert_eq!(
28+
u8x4::load_select(
29+
&arr[4..],
30+
Mask::from_array([true, false, true, true]),
31+
u8x4::splat(42)
32+
),
33+
u8x4::from_array([3, 42, 0, 42])
34+
);
35+
}

0 commit comments

Comments
 (0)