use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Sub};
use crate::backend::Backend;
use crate::scalar::{FloatScalar, IntScalar, Scalar};
#[cfg(hp_resolved_unroll)]
pub(crate) const STATIC_UNROLL: usize = {
const fn parse(s: &str) -> usize {
let b = s.as_bytes();
let (mut v, mut i) = (0usize, 0usize);
while i < b.len() {
v = v * 10 + (b[i] - b'0') as usize;
i += 1;
}
v
}
match option_env!("HP_STATIC_UNROLL") {
Some(s) => parse(s),
None => 4,
}
};
#[cfg(not(any(hp_no_ilp, target_arch = "spirv")))]
#[inline(always)]
fn unroll_k<T: Scalar, S: Backend<T>>() -> usize {
<S as Backend<T>>::UNROLL
}
#[cfg(any(hp_no_ilp, target_arch = "spirv"))]
#[inline(always)]
#[allow(clippy::extra_unused_type_parameters)] fn unroll_k<T: Scalar, S: Backend<T>>() -> usize {
1
}
#[inline(always)]
unsafe fn head_unchecked<E>(s: &[E], len: usize) -> &[E] {
debug_assert!(len <= s.len());
unsafe { s.get_unchecked(..len) }
}
#[inline(always)]
unsafe fn head_mut_unchecked<E>(s: &mut [E], len: usize) -> &mut [E] {
debug_assert!(len <= s.len());
unsafe { s.get_unchecked_mut(..len) }
}
#[inline(always)]
fn chain_cap(live: usize) -> usize {
let budget = 32 / live.max(1);
if budget == 0 {
1
} else {
1 << (usize::BITS - 1 - budget.leading_zeros())
}
}
#[derive(Clone, Copy)]
pub struct Gang<S> {
backend: S,
}
impl<S: Copy> Gang<S> {
#[inline(always)]
pub(crate) fn new(backend: S) -> Self {
Self { backend }
}
#[inline(always)]
pub fn lanes<T: Scalar>(self) -> usize
where
S: Backend<T>,
{
<S as Backend<T>>::lanes(self.backend)
}
#[inline(always)]
pub fn splat<T: Scalar>(self, v: T) -> Varying<T, S>
where
S: Backend<T>,
{
Varying::wrap(self.backend, self.backend.splat(v))
}
#[inline(always)]
pub fn load<T: Scalar>(self, s: &[T]) -> Varying<T, S>
where
S: Backend<T>,
{
assert!(
s.len() == self.backend.lanes(),
"Gang::load: slice length must equal lanes()",
);
Varying::wrap(self.backend, self.backend.load(s))
}
#[inline]
pub fn for_each_chunk<T: Scalar>(self, len: usize, mut f: impl FnMut(usize, usize))
where
S: Backend<T>,
{
let n = self.backend.lanes();
let mut off = 0;
while off + n <= len {
f(off, n);
off += n;
}
if off < len {
f(off, len - off);
}
}
#[inline]
pub fn chunks_exact<T: Scalar>(self, len: usize) -> ChunksExact
where
S: Backend<T>,
{
ChunksExact {
lanes: self.backend.lanes(),
pos: 0,
len,
}
}
#[inline]
pub fn remainder<T: Scalar>(self, len: usize) -> Option<(usize, usize)>
where
S: Backend<T>,
{
let cnt = len % self.backend.lanes();
(cnt != 0).then_some((len - cnt, cnt))
}
#[inline(always)]
pub fn splat_u32<T: Scalar>(self, v: u32) -> VaryingU32<T, S>
where
S: Backend<T>,
{
VaryingU32::wrap(self.backend, self.backend.isplat(v))
}
#[inline(always)]
pub fn load_u32<T: Scalar>(self, s: &[u32]) -> VaryingU32<T, S>
where
S: Backend<T>,
{
assert!(
s.len() == self.backend.lanes(),
"Gang::load_u32: slice length must equal lanes()",
);
VaryingU32::wrap(self.backend, self.backend.iload(s))
}
#[inline(always)]
pub fn ramp_u32<T: Scalar>(self) -> VaryingU32<T, S>
where
S: Backend<T>,
{
VaryingU32::wrap(self.backend, self.backend.iramp())
}
#[inline(always)]
pub fn splat_i32<T: Scalar>(self, v: i32) -> VaryingI32<T, S>
where
S: Backend<T>,
{
self.splat_u32(v as u32).as_i32()
}
#[inline]
pub fn load_i32<T: Scalar>(self, s: &[i32]) -> VaryingI32<T, S>
where
S: Backend<T>,
{
let n = self.backend.lanes();
assert!(s.len() == n, "Gang::load_i32: slice length must equal lanes()");
let mut buf = [0u32; crate::MAX_LANES];
for (b, &x) in buf[..n].iter_mut().zip(s) {
*b = x as u32;
}
VaryingU32::wrap(self.backend, self.backend.iload(&buf[..n])).as_i32()
}
#[inline(always)]
pub fn from_bits<T: Scalar>(self, v: VaryingU32<T, S>) -> Varying<T, S>
where
S: Backend<T>,
{
v.to_float_bits()
}
#[inline]
pub fn load_partial<T: Scalar>(self, s: &[T], fill: T) -> Varying<T, S>
where
S: Backend<T>,
{
let n = self.backend.lanes();
debug_assert!(s.len() <= n, "Gang::load_partial: slice longer than lanes()");
if s.len() == n {
self.load(s)
} else {
self.load_tail(s, fill)
}
}
#[cold]
#[inline(never)]
fn load_tail<T: Scalar>(self, s: &[T], fill: T) -> Varying<T, S>
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = s.len();
let mut buf = [fill; crate::MAX_LANES];
for i in 0..n {
if i < len {
unsafe {
*buf.get_unchecked_mut(i) = *s.get_unchecked(i);
}
}
}
self.load(&buf[..n])
}
#[inline(always)]
pub fn backend(self) -> S {
self.backend
}
#[inline]
pub fn active_mask<T: Scalar>(self, cnt: usize) -> Mask<T, S>
where
S: Backend<T>,
{
let n = self.backend.lanes();
debug_assert!(cnt <= n, "Gang::active_mask: cnt must not exceed lanes()");
let mut ramp = [T::ZERO; crate::MAX_LANES];
for (i, slot) in ramp[..n].iter_mut().enumerate() {
*slot = T::from_f64(i as f64);
}
self.load(&ramp[..n]).lt(self.splat(T::from_f64(cnt as f64)))
}
#[inline]
pub fn fold<T: Scalar, A>(
self,
a: &[T],
fill: T,
init: A,
mut f: impl FnMut(A, Varying<T, S>) -> A,
) -> A
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len();
let mut acc = init;
let mut off = 0;
while off + n <= len {
acc = f(acc, self.load(&a[off..off + n]));
off += n;
}
if off < len {
acc = f(acc, self.load_partial(&a[off..len], fill));
}
acc
}
#[inline]
pub fn zip_fold<T: Scalar, A>(
self,
a: &[T],
b: &[T],
fill_a: T,
fill_b: T,
init: A,
mut f: impl FnMut(A, Varying<T, S>, Varying<T, S>) -> A,
) -> A
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len().min(b.len());
let mut acc = init;
let mut off = 0;
while off + n <= len {
let va = self.load(&a[off..off + n]);
let vb = self.load(&b[off..off + n]);
acc = f(acc, va, vb);
off += n;
}
if off < len {
let va = self.load_partial(&a[off..len], fill_a);
let vb = self.load_partial(&b[off..len], fill_b);
acc = f(acc, va, vb);
}
acc
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn zip3_fold<T: Scalar, A>(
self,
a: &[T],
b: &[T],
c: &[T],
fill_a: T,
fill_b: T,
fill_c: T,
init: A,
mut f: impl FnMut(A, Varying<T, S>, Varying<T, S>, Varying<T, S>) -> A,
) -> A
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len().min(b.len()).min(c.len());
let mut acc = init;
let mut off = 0;
while off + n <= len {
let va = self.load(&a[off..off + n]);
let vb = self.load(&b[off..off + n]);
let vc = self.load(&c[off..off + n]);
acc = f(acc, va, vb, vc);
off += n;
}
if off < len {
let va = self.load_partial(&a[off..len], fill_a);
let vb = self.load_partial(&b[off..len], fill_b);
let vc = self.load_partial(&c[off..len], fill_c);
acc = f(acc, va, vb, vc);
}
acc
}
#[inline]
pub fn map<T: Scalar>(self, a: &[T], out: &mut [T], fill: T, mut f: impl FnMut(Varying<T, S>) -> Varying<T, S>)
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len().min(out.len());
let k = unroll_k::<T, S>();
let mut off = 0;
while off + k * n <= len {
let mut j = 0;
while j < k {
let o = off + j * n;
f(self.load(&a[o..o + n])).store(&mut out[o..o + n]);
j += 1;
}
off += k * n;
}
while off + n <= len {
f(self.load(&a[off..off + n])).store(&mut out[off..off + n]);
off += n;
}
if off < len {
f(self.load_partial(&a[off..len], fill)).store_partial(&mut out[off..len]);
}
}
#[inline]
pub fn zip_map<T: Scalar>(
self,
a: &[T],
b: &[T],
out: &mut [T],
fill_a: T,
fill_b: T,
mut f: impl FnMut(Varying<T, S>, Varying<T, S>) -> Varying<T, S>,
)
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len().min(b.len()).min(out.len());
let k = unroll_k::<T, S>();
let mut off = 0;
while off + k * n <= len {
let mut j = 0;
while j < k {
let o = off + j * n;
let va = self.load(&a[o..o + n]);
let vb = self.load(&b[o..o + n]);
f(va, vb).store(&mut out[o..o + n]);
j += 1;
}
off += k * n;
}
while off + n <= len {
let va = self.load(&a[off..off + n]);
let vb = self.load(&b[off..off + n]);
f(va, vb).store(&mut out[off..off + n]);
off += n;
}
if off < len {
let va = self.load_partial(&a[off..len], fill_a);
let vb = self.load_partial(&b[off..len], fill_b);
f(va, vb).store_partial(&mut out[off..len]);
}
}
#[inline]
pub fn zip_map_inplace<T: Scalar>(
self,
a: &[T],
b: &mut [T],
fill_a: T,
fill_b: T,
mut f: impl FnMut(Varying<T, S>, Varying<T, S>) -> Varying<T, S>,
)
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len().min(b.len());
let k = unroll_k::<T, S>();
let mut off = 0;
while off + k * n <= len {
let mut j = 0;
while j < k {
let o = off + j * n;
let va = self.load(&a[o..o + n]);
let vb = self.load(&b[o..o + n]);
f(va, vb).store(&mut b[o..o + n]);
j += 1;
}
off += k * n;
}
while off + n <= len {
let va = self.load(&a[off..off + n]);
let vb = self.load(&b[off..off + n]);
f(va, vb).store(&mut b[off..off + n]);
off += n;
}
if off < len {
let va = self.load_partial(&a[off..len], fill_a);
let vb = self.load_partial(&b[off..len], fill_b);
f(va, vb).store_partial(&mut b[off..len]);
}
}
#[inline]
pub fn stream_map<T: Scalar>(self, a: &[T], out: &mut [T], mut f: impl FnMut(T) -> T)
where
S: Backend<T>,
{
for (o, &x) in out.iter_mut().zip(a) {
*o = f(x);
}
}
#[inline]
pub fn stream_zip<T: Scalar>(self, a: &[T], b: &[T], out: &mut [T], mut f: impl FnMut(T, T) -> T)
where
S: Backend<T>,
{
for (o, (&x, &y)) in out.iter_mut().zip(a.iter().zip(b)) {
*o = f(x, y);
}
}
#[inline]
pub fn stream_zip_inplace<T: Scalar>(self, a: &[T], b: &mut [T], mut f: impl FnMut(T, T) -> T)
where
S: Backend<T>,
{
for (bi, &x) in b.iter_mut().zip(a) {
*bi = f(x, *bi);
}
}
#[inline]
pub fn map_n<T: Scalar, const N: usize>(
self,
cols: [&mut [T]; N],
fill: T,
mut f: impl FnMut([Varying<T, S>; N]) -> [Varying<T, S>; N],
)
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = cols.iter().map(|c| c.len()).min().unwrap_or(0);
let cols: [&mut [T]; N] = cols.map(|c| unsafe { head_mut_unchecked(c, len) });
let k = unroll_k::<T, S>().min(chain_cap(2 * N));
let mut off = 0;
while off + k * n <= len {
let mut j = 0;
while j < k {
let o = off + j * n;
let rs = f(core::array::from_fn(|c| self.load(&cols[c][o..o + n])));
for c in 0..N {
rs[c].store(&mut cols[c][o..o + n]);
}
j += 1;
}
off += k * n;
}
while off + n <= len {
let rs = f(core::array::from_fn(|c| self.load(&cols[c][off..off + n])));
for c in 0..N {
rs[c].store(&mut cols[c][off..off + n]);
}
off += n;
}
if off < len {
let rs = f(core::array::from_fn(|c| self.load_partial(&cols[c][off..len], fill)));
for c in 0..N {
rs[c].store_partial(&mut cols[c][off..len]);
}
}
}
#[inline]
pub fn map_cols<T: Scalar, const IN: usize, const OUT: usize>(
self,
inp: [&[T]; IN],
out: [&mut [T]; OUT],
fill: T,
mut f: impl FnMut([Varying<T, S>; IN]) -> [Varying<T, S>; OUT],
)
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = inp
.iter()
.map(|c| c.len())
.chain(out.iter().map(|c| c.len()))
.min()
.unwrap_or(0);
let inp: [&[T]; IN] = inp.map(|c| unsafe { head_unchecked(c, len) });
let out: [&mut [T]; OUT] = out.map(|c| unsafe { head_mut_unchecked(c, len) });
let k = unroll_k::<T, S>().min(chain_cap(IN + OUT));
let mut off = 0;
while off + k * n <= len {
let mut j = 0;
while j < k {
let o = off + j * n;
let rs = f(core::array::from_fn(|c| self.load(&inp[c][o..o + n])));
for c in 0..OUT {
rs[c].store(&mut out[c][o..o + n]);
}
j += 1;
}
off += k * n;
}
while off + n <= len {
let rs = f(core::array::from_fn(|c| self.load(&inp[c][off..off + n])));
for c in 0..OUT {
rs[c].store(&mut out[c][off..off + n]);
}
off += n;
}
if off < len {
let rs = f(core::array::from_fn(|c| self.load_partial(&inp[c][off..len], fill)));
for c in 0..OUT {
rs[c].store_partial(&mut out[c][off..len]);
}
}
}
#[inline]
pub fn any<T: Scalar>(self, a: &[T], fill: T, mut pred: impl FnMut(Varying<T, S>) -> Mask<T, S>) -> bool
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len();
let mut off = 0;
while off + n <= len {
if pred(self.load(&a[off..off + n])).any() {
return true;
}
off += n;
}
off < len && pred(self.load_partial(&a[off..len], fill)).any()
}
#[inline]
pub fn all<T: Scalar>(self, a: &[T], fill: T, mut pred: impl FnMut(Varying<T, S>) -> Mask<T, S>) -> bool
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len();
let mut off = 0;
while off + n <= len {
if !pred(self.load(&a[off..off + n])).all() {
return false;
}
off += n;
}
off >= len || pred(self.load_partial(&a[off..len], fill)).all()
}
#[inline]
pub fn zip_any<T: Scalar>(
self,
a: &[T],
b: &[T],
fill_a: T,
fill_b: T,
mut pred: impl FnMut(Varying<T, S>, Varying<T, S>) -> Mask<T, S>,
) -> bool
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len().min(b.len());
let mut off = 0;
while off + n <= len {
if pred(self.load(&a[off..off + n]), self.load(&b[off..off + n])).any() {
return true;
}
off += n;
}
if off < len {
let va = self.load_partial(&a[off..len], fill_a);
let vb = self.load_partial(&b[off..len], fill_b);
return pred(va, vb).any();
}
false
}
#[inline]
pub fn zip_all<T: Scalar>(
self,
a: &[T],
b: &[T],
fill_a: T,
fill_b: T,
mut pred: impl FnMut(Varying<T, S>, Varying<T, S>) -> Mask<T, S>,
) -> bool
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = a.len().min(b.len());
let mut off = 0;
while off + n <= len {
if !pred(self.load(&a[off..off + n]), self.load(&b[off..off + n])).all() {
return false;
}
off += n;
}
if off < len {
let va = self.load_partial(&a[off..len], fill_a);
let vb = self.load_partial(&b[off..len], fill_b);
return pred(va, vb).all();
}
true
}
#[inline]
pub fn any_n<T: Scalar, const N: usize>(
self,
cols: [&[T]; N],
mut pred: impl FnMut([Varying<T, S>; N]) -> Mask<T, S>,
) -> bool
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = if N == 0 { 0 } else { cols[0].len() };
let mut off = 0;
while off + n <= len {
let vs = core::array::from_fn(|j| self.load(&cols[j][off..off + n]));
if pred(vs).any() {
return true;
}
off += n;
}
if off < len {
let cnt = len - off;
let vs = core::array::from_fn(|j| self.load_partial(&cols[j][off..len], T::ZERO));
return (pred(vs) & self.active_mask(cnt)).any();
}
false
}
#[inline]
pub fn all_n<T: Scalar, const N: usize>(
self,
cols: [&[T]; N],
mut pred: impl FnMut([Varying<T, S>; N]) -> Mask<T, S>,
) -> bool
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = if N == 0 { 0 } else { cols[0].len() };
let mut off = 0;
while off + n <= len {
let vs = core::array::from_fn(|j| self.load(&cols[j][off..off + n]));
if !pred(vs).all() {
return false;
}
off += n;
}
if off < len {
let cnt = len - off;
let vs = core::array::from_fn(|j| self.load_partial(&cols[j][off..len], T::ZERO));
return (pred(vs) | !self.active_mask(cnt)).all();
}
true
}
#[inline]
#[cfg(not(any(hp_no_ilp, target_arch = "spirv")))]
pub fn count_n<T: Scalar, const N: usize>(
self,
cols: [&[T]; N],
mut pred: impl FnMut([Varying<T, S>; N]) -> Mask<T, S>,
) -> usize
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = if N == 0 { 0 } else { cols[0].len() };
let k = <S as Backend<T>>::UNROLL;
if k == 1 || len / n < 8 {
return self.count_n_fold(cols, pred).reduce_sum().into_f64() as usize;
}
let one = self.splat(T::ONE);
let zero = self.splat(T::ZERO);
let mut acc = [zero; crate::MAX_UNROLL];
let mut off = 0;
while off + k * n <= len {
let w: [&[T]; N] = core::array::from_fn(|c| &cols[c][off..off + k * n]);
let mut j = 0;
while j < k {
let o = j * n;
let vs = core::array::from_fn(|c| self.load(&w[c][o..o + n]));
acc[j] = acc[j] + one.select(pred(vs), zero);
j += 1;
}
off += k * n;
}
let mut j = 0;
while off + n <= len {
let vs = core::array::from_fn(|c| self.load(&cols[c][off..off + n]));
acc[j] = acc[j] + one.select(pred(vs), zero);
off += n;
j += 1;
}
let mut width = k;
while width > 1 {
let half = width / 2;
let mut j = 0;
while j < half {
acc[j] = acc[j] + acc[width - half + j];
j += 1;
}
width -= half;
}
let mut result = acc[0];
if off < len {
let cnt = len - off;
let vs = core::array::from_fn(|c| self.load_partial(&cols[c][off..len], T::ZERO));
let mask = pred(vs) & self.active_mask(cnt);
result = result + one.select(mask, zero);
}
result.reduce_sum().into_f64() as usize
}
#[inline]
#[cfg(any(hp_no_ilp, target_arch = "spirv"))]
pub fn count_n<T: Scalar, const N: usize>(
self,
cols: [&[T]; N],
pred: impl FnMut([Varying<T, S>; N]) -> Mask<T, S>,
) -> usize
where
S: Backend<T>,
{
self.count_n_fold(cols, pred).reduce_sum().into_f64() as usize
}
#[inline]
fn count_n_fold<T: Scalar, const N: usize>(
self,
cols: [&[T]; N],
mut pred: impl FnMut([Varying<T, S>; N]) -> Mask<T, S>,
) -> Varying<T, S>
where
S: Backend<T>,
{
let n = self.backend.lanes();
let len = if N == 0 { 0 } else { cols[0].len() };
let one = self.splat(T::ONE);
let zero = self.splat(T::ZERO);
let mut acc = zero;
let mut off = 0;
while off + n <= len {
let vs = core::array::from_fn(|j| self.load(&cols[j][off..off + n]));
acc = acc + one.select(pred(vs), zero);
off += n;
}
if off < len {
let cnt = len - off;
let vs = core::array::from_fn(|j| self.load_partial(&cols[j][off..len], T::ZERO));
let mask = pred(vs) & self.active_mask(cnt);
acc = acc + one.select(mask, zero);
}
acc
}
#[inline]
pub fn for_each_hit_n<T: Scalar, const N: usize>(
self,
cols: [&[T]; N],
mut pred: impl FnMut([Varying<T, S>; N]) -> Mask<T, S>,
mut on_hit: impl FnMut(usize),
) -> bool
where
S: Backend<T>,
{
let len = if N == 0 { 0 } else { cols[0].len() };
let mut any = false;
for (off, cnt, active) in self.masked_chunks(len) {
let vs = core::array::from_fn(|j| self.load_partial(&cols[j][off..off + cnt], T::ZERO));
let mut bits = (pred(vs) & active).to_bitmask();
any |= bits != 0;
while bits != 0 {
on_hit(off + bits.trailing_zeros() as usize);
bits &= bits - 1;
}
}
any
}
#[inline]
pub fn masked_chunks<T: Scalar>(self, len: usize) -> impl Iterator<Item = (usize, usize, Mask<T, S>)>
where
S: Backend<T>,
{
let n = self.backend.lanes();
self.chunks_exact(len)
.map(move |off| (off, n, self.active_mask(n)))
.chain(
self.remainder(len)
.map(|(off, cnt)| (off, cnt, self.active_mask(cnt))),
)
}
#[inline]
pub fn splat_n<T: Scalar, const N: usize>(self, vals: [T; N]) -> [Varying<T, S>; N]
where
S: Backend<T>,
{
core::array::from_fn(|i| self.splat(vals[i]))
}
#[inline]
pub fn load_n<T: Scalar, const N: usize>(self, cols: [&[T]; N]) -> [Varying<T, S>; N]
where
S: Backend<T>,
{
core::array::from_fn(|i| self.load(cols[i]))
}
#[inline]
pub fn load_partial_n<T: Scalar, const N: usize>(self, cols: [&[T]; N], fill: T) -> [Varying<T, S>; N]
where
S: Backend<T>,
{
core::array::from_fn(|i| self.load_partial(cols[i], fill))
}
#[inline]
#[allow(clippy::needless_range_loop)]
pub fn gather_n<T: Scalar, E, const N: usize>(
self,
items: &[E],
fills: [T; N],
mut extract: impl FnMut(&E) -> [T; N],
) -> [Varying<T, S>; N]
where
S: Backend<T>,
{
let n = self.backend.lanes();
let cnt = items.len();
debug_assert!(cnt <= n, "Gang::gather_n: more rows than lanes()");
let mut scratch = [[MaybeUninit::<T>::uninit(); crate::MAX_LANES]; N];
for (i, item) in items.iter().enumerate() {
let row = extract(item);
for c in 0..N {
scratch[c][i] = MaybeUninit::new(row[c]);
}
}
if cnt < n {
for c in 0..N {
for slot in &mut scratch[c][cnt..n] {
*slot = MaybeUninit::new(fills[c]);
}
}
}
core::array::from_fn(|c| {
let lane = unsafe { core::slice::from_raw_parts(scratch[c].as_ptr().cast::<T>(), n) };
self.load(lane)
})
}
#[inline]
#[allow(clippy::too_many_arguments)]
#[cfg(not(any(hp_no_ilp, target_arch = "spirv")))]
pub fn zip_reduce<T: Scalar, A: Copy, FS, FC>(
self,
a: &[T],
b: &[T],
fill_a: T,
fill_b: T,
init: A,
step: FS,
combine: FC,
) -> A
where
S: Backend<T>,
FS: Fn(A, Varying<T, S>, Varying<T, S>) -> A,
FC: Fn(A, A) -> A,
{
let n = self.backend.lanes();
let len = a.len().min(b.len());
let k = <S as Backend<T>>::UNROLL;
if k == 1 || len / n < 8 {
return self.zip_fold(a, b, fill_a, fill_b, init, step);
}
let mut acc = [init; crate::MAX_UNROLL];
let mut off = 0;
while off + k * n <= len {
let aw = &a[off..off + k * n];
let bw = &b[off..off + k * n];
let mut j = 0;
while j < k {
let o = j * n;
acc[j] = step(acc[j], self.load(&aw[o..o + n]), self.load(&bw[o..o + n]));
j += 1;
}
off += k * n;
}
let mut j = 0;
while off + n <= len {
acc[j] = step(acc[j], self.load(&a[off..off + n]), self.load(&b[off..off + n]));
off += n;
j += 1;
}
let mut width = k;
while width > 1 {
let half = width / 2;
let mut j = 0;
while j < half {
acc[j] = combine(acc[j], acc[width - half + j]);
j += 1;
}
width -= half;
}
let mut result = acc[0];
if off < len {
let va = self.load_partial(&a[off..len], fill_a);
let vb = self.load_partial(&b[off..len], fill_b);
result = step(result, va, vb);
}
result
}
#[inline]
#[allow(clippy::too_many_arguments)]
#[cfg(any(hp_no_ilp, target_arch = "spirv"))]
pub fn zip_reduce<T: Scalar, A: Copy, FS, FC>(
self,
a: &[T],
b: &[T],
fill_a: T,
fill_b: T,
init: A,
step: FS,
combine: FC,
) -> A
where
S: Backend<T>,
FS: Fn(A, Varying<T, S>, Varying<T, S>) -> A,
FC: Fn(A, A) -> A,
{
let _ = combine;
self.zip_fold(a, b, fill_a, fill_b, init, step)
}
#[inline]
#[cfg(not(any(hp_no_ilp, target_arch = "spirv")))]
pub fn reduce<T: Scalar, A: Copy, FS, FC>(self, a: &[T], fill: T, init: A, step: FS, combine: FC) -> A
where
S: Backend<T>,
FS: Fn(A, Varying<T, S>) -> A,
FC: Fn(A, A) -> A,
{
let n = self.backend.lanes();
let len = a.len();
let k = <S as Backend<T>>::UNROLL;
if k == 1 || len / n < 8 {
return self.fold(a, fill, init, step);
}
let mut acc = [init; crate::MAX_UNROLL];
let mut off = 0;
while off + k * n <= len {
let aw = &a[off..off + k * n];
let mut j = 0;
while j < k {
let o = j * n;
acc[j] = step(acc[j], self.load(&aw[o..o + n]));
j += 1;
}
off += k * n;
}
let mut j = 0;
while off + n <= len {
acc[j] = step(acc[j], self.load(&a[off..off + n]));
off += n;
j += 1;
}
let mut width = k;
while width > 1 {
let half = width / 2;
let mut j = 0;
while j < half {
acc[j] = combine(acc[j], acc[width - half + j]);
j += 1;
}
width -= half;
}
let mut result = acc[0];
if off < len {
result = step(result, self.load_partial(&a[off..len], fill));
}
result
}
#[inline]
#[cfg(any(hp_no_ilp, target_arch = "spirv"))]
pub fn reduce<T: Scalar, A: Copy, FS, FC>(self, a: &[T], fill: T, init: A, step: FS, combine: FC) -> A
where
S: Backend<T>,
FS: Fn(A, Varying<T, S>) -> A,
FC: Fn(A, A) -> A,
{
let _ = combine;
self.fold(a, fill, init, step)
}
#[inline]
pub fn zip_sum<T: Scalar, F>(self, a: &[T], b: &[T], step: F) -> T
where
S: Backend<T>,
F: Fn(Varying<T, S>, Varying<T, S>, Varying<T, S>) -> Varying<T, S>,
{
self.zip_reduce(a, b, T::ZERO, T::ZERO, self.splat(T::ZERO), step, |p, q| p + q)
.reduce_sum()
}
#[inline]
pub fn sum<T: Scalar, F>(self, a: &[T], step: F) -> T
where
S: Backend<T>,
F: Fn(Varying<T, S>, Varying<T, S>) -> Varying<T, S>,
{
self.reduce(a, T::ZERO, self.splat(T::ZERO), step, |p, q| p + q)
.reduce_sum()
}
#[inline]
pub fn total<T: Scalar>(self, a: &[T]) -> T
where
S: Backend<T>,
{
self.sum(a, |acc, x| acc + x)
}
#[inline]
pub fn dot<T: FloatScalar>(self, a: &[T], b: &[T]) -> T
where
S: Backend<T>,
{
self.zip_sum(a, b, |acc, x, y| x.fma(y, acc))
}
#[inline]
pub fn norm_sq<T: FloatScalar>(self, a: &[T]) -> T
where
S: Backend<T>,
{
self.sum(a, |acc, x| x.fma(x, acc))
}
#[inline]
pub fn norm<T: FloatScalar>(self, a: &[T]) -> T
where
S: Backend<T>,
{
self.norm_sq(a).sqrt()
}
#[inline]
#[cfg(all(not(hp_no_ilp), not(target_arch = "spirv"), not(hp_resolved_unroll)))]
pub(crate) fn unroll(self) -> usize
where
S: Backend<f32>,
{
if <S as Backend<f32>>::lanes(self.backend) == 1 {
return 1;
}
match crate::ilp::cached() {
0 => self.detect_unroll(),
k => k as usize,
}
}
#[inline]
#[cfg(all(not(hp_no_ilp), not(target_arch = "spirv"), not(hp_resolved_unroll)))]
pub(crate) fn unroll_for<T: Scalar>(self) -> usize
where
S: Backend<T>,
{
if <S as Backend<T>>::lanes(self.backend) == 1 {
return 1;
}
match crate::ilp::cached() {
0 => <S as Backend<T>>::UNROLL,
k => k as usize,
}
}
#[inline(always)]
#[cfg(hp_resolved_unroll)]
pub(crate) fn unroll_for<T: Scalar>(self) -> usize
where
S: Backend<T>,
{
if <S as Backend<T>>::lanes(self.backend) == 1 {
return 1;
}
STATIC_UNROLL
}
#[inline(always)]
#[cfg(any(hp_no_ilp, target_arch = "spirv"))]
pub(crate) fn unroll_for<T: Scalar>(self) -> usize
where
S: Backend<T>,
{
1
}
#[inline(always)]
#[cfg(hp_resolved_unroll)]
pub(crate) fn unroll(self) -> usize
where
S: Backend<f32>,
{
if <S as Backend<f32>>::lanes(self.backend) == 1 {
return 1;
}
STATIC_UNROLL
}
#[inline(always)]
#[cfg(any(hp_no_ilp, target_arch = "spirv"))]
pub(crate) fn unroll(self) -> usize
where
S: Backend<f32>,
{
1
}
#[cfg(all(feature = "std", not(hp_no_ilp), not(target_arch = "spirv"), not(hp_resolved_unroll)))]
#[cold]
#[inline(never)]
fn detect_unroll(self) -> usize
where
S: Backend<f32>,
{
use std::hint::black_box;
use std::time::Instant;
use crate::backend::Unroll;
let probe: std::vec::Vec<f32> = (0..4096)
.map(|i| (i % 17) as f32 * 0.5 - 4.0)
.collect();
let a = probe.as_slice();
let zero = 0.0f32;
let b = self.backend();
macro_rules! time_k {
($k:literal, $iters:expr) => {{
let g = Gang::new(Unroll::<S, $k>(b));
let init = g.splat(zero);
let mut best = u64::MAX;
for _ in 0..3 {
let t = Instant::now();
let mut sink = 0.0f64;
for _ in 0..$iters {
let r = g.zip_reduce(
black_box(a),
black_box(a),
zero,
zero,
init,
|acc, x, y| x.madd(y, acc),
|p, q| p + q,
);
sink += r.reduce_sum().into_f64();
}
black_box(sink);
let e = t.elapsed().as_nanos() as u64;
if e < best {
best = e;
}
}
best
}};
}
let one_ns = time_k!(1, 1).max(1);
let iters = (500_000u64 / one_ns).clamp(1, 100_000) as u32;
let cands = [
(1u8, time_k!(1, iters)),
(2u8, time_k!(2, iters)),
(4u8, time_k!(4, iters)),
(8u8, time_k!(8, iters)),
(12u8, time_k!(12, iters)),
(16u8, time_k!(16, iters)),
];
let mut best = cands[0];
for &c in &cands[1..] {
if c.1 < best.1 {
best = c;
}
}
crate::ilp::store(best.0);
best.0 as usize
}
#[cfg(all(not(feature = "std"), not(hp_no_ilp), not(target_arch = "spirv"), not(hp_resolved_unroll)))]
#[cold]
#[inline(never)]
fn detect_unroll(self) -> usize
where
S: Backend<f32>,
{
let _ = self;
let k: u8 = if cfg!(target_arch = "aarch64") {
8
} else if cfg!(any(target_arch = "x86_64", target_arch = "x86")) {
4
} else {
1
};
crate::ilp::store(k);
k as usize
}
}
#[derive(Clone, Copy, Debug)]
pub struct ChunksExact {
lanes: usize,
pos: usize,
len: usize,
}
impl ChunksExact {
#[inline]
pub fn remainder(self) -> Option<(usize, usize)> {
let cnt = self.len % self.lanes;
(cnt != 0).then_some((self.len - cnt, cnt))
}
}
impl Iterator for ChunksExact {
type Item = usize;
#[inline]
fn next(&mut self) -> Option<usize> {
if self.pos + self.lanes <= self.len {
let off = self.pos;
self.pos += self.lanes;
Some(off)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.len - self.pos.min(self.len)) / self.lanes;
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for ChunksExact {}
#[derive(Clone, Copy)]
pub struct Varying<T: Scalar, S: Backend<T>> {
backend: S,
_t: PhantomData<T>,
v: S::Vector,
}
impl<T: Scalar, S: Backend<T>> Varying<T, S> {
#[inline(always)]
fn wrap(backend: S, v: S::Vector) -> Self {
Self {
backend,
v,
_t: PhantomData,
}
}
#[inline(always)]
pub fn raw(self) -> S::Vector {
self.v
}
#[inline(always)]
pub fn store(self, out: &mut [T]) {
assert!(
out.len() == self.backend.lanes(),
"Varying::store: slice length must equal lanes()",
);
self.backend.store(self.v, out)
}
#[inline]
pub fn store_partial(self, out: &mut [T]) {
let n = self.backend.lanes();
debug_assert!(out.len() <= n, "Varying::store_partial: slice longer than lanes()");
if out.len() == n {
self.backend.store(self.v, out);
return;
}
let mut buf = [T::ZERO; crate::MAX_LANES];
self.backend.store(self.v, &mut buf[..n]);
out.copy_from_slice(&buf[..out.len()]);
}
#[inline(always)]
pub fn sqrt(self) -> Self
where
T: FloatScalar,
{
Self::wrap(self.backend, self.backend.sqrt(self.v))
}
#[inline(always)]
pub fn recip(self) -> Self
where
T: FloatScalar,
{
let one = self.backend.splat(T::ONE);
Self::wrap(self.backend, self.backend.div(one, self.v))
}
#[inline(always)]
pub fn abs(self) -> Self {
Self::wrap(self.backend, self.backend.abs(self.v))
}
#[inline(always)]
pub fn min(self, o: Self) -> Self {
Self::wrap(self.backend, self.backend.min(self.v, o.v))
}
#[inline(always)]
pub fn max(self, o: Self) -> Self {
Self::wrap(self.backend, self.backend.max(self.v, o.v))
}
#[inline(always)]
pub fn fma(self, b: Self, c: Self) -> Self
where
T: FloatScalar,
{
Self::wrap(self.backend, self.backend.fma(self.v, b.v, c.v))
}
#[inline(always)]
pub fn madd(self, b: Self, acc: Self) -> Self {
Self::wrap(self.backend, self.backend.madd(self.v, b.v, acc.v))
}
#[inline(always)]
pub fn to_bits(self) -> VaryingU32<T, S> {
VaryingU32::wrap(self.backend, self.backend.to_bits(self.v))
}
#[inline(always)]
pub fn le(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.le(self.v, o.v))
}
#[inline(always)]
pub fn lt(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.lt(self.v, o.v))
}
#[inline(always)]
pub fn ge(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.ge(self.v, o.v))
}
#[inline(always)]
pub fn gt(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.gt(self.v, o.v))
}
#[inline(always)]
pub fn select(self, mask: Mask<T, S>, other: Self) -> Self {
Self::wrap(self.backend, self.backend.select(mask.m, self.v, other.v))
}
#[inline(always)]
pub fn reduce_sum(self) -> T {
self.backend.reduce_sum(self.v)
}
#[inline(always)]
pub fn reduce_min(self) -> T {
self.backend.reduce_min(self.v)
}
#[inline(always)]
pub fn reduce_max(self) -> T {
self.backend.reduce_max(self.v)
}
}
macro_rules! lane_binop {
($trait:ident, $method:ident, $bk:ident) => {
impl<T: Scalar, S: Backend<T>> $trait for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn $method(self, rhs: Self) -> Self {
Varying::wrap(self.backend, self.backend.$bk(self.v, rhs.v))
}
}
};
}
lane_binop!(Add, add, add);
lane_binop!(Sub, sub, sub);
lane_binop!(Mul, mul, mul);
impl<T: FloatScalar, S: Backend<T>> Div for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn div(self, rhs: Self) -> Self {
Varying::wrap(self.backend, self.backend.div(self.v, rhs.v))
}
}
impl<T: Scalar, S: Backend<T>> Neg for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn neg(self) -> Self {
Varying::wrap(self.backend, self.backend.neg(self.v))
}
}
macro_rules! varying_scalar_binop {
($trait:ident, $method:ident, $bk:ident) => {
impl<T: Scalar, S: Backend<T>> $trait<T> for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn $method(self, rhs: T) -> Self {
let r = self.backend.splat(rhs);
Varying::wrap(self.backend, self.backend.$bk(self.v, r))
}
}
};
}
varying_scalar_binop!(Add, add, add);
varying_scalar_binop!(Sub, sub, sub);
varying_scalar_binop!(Mul, mul, mul);
impl<T: FloatScalar, S: Backend<T>> Div<T> for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn div(self, rhs: T) -> Self {
let r = self.backend.splat(rhs);
Varying::wrap(self.backend, self.backend.div(self.v, r))
}
}
impl<T: IntScalar, S: Backend<T>> core::ops::Shl<u32> for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn shl(self, k: u32) -> Self {
Varying::wrap(self.backend, self.backend.shl(self.v, k))
}
}
impl<T: IntScalar, S: Backend<T>> core::ops::Shr<u32> for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn shr(self, k: u32) -> Self {
Varying::wrap(self.backend, self.backend.shr(self.v, k))
}
}
impl<T: IntScalar, S: Backend<T>> BitAnd for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn bitand(self, rhs: Self) -> Self {
Varying::wrap(self.backend, self.backend.bit_and(self.v, rhs.v))
}
}
impl<T: IntScalar, S: Backend<T>> BitOr for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn bitor(self, rhs: Self) -> Self {
Varying::wrap(self.backend, self.backend.bit_or(self.v, rhs.v))
}
}
impl<T: IntScalar, S: Backend<T>> BitXor for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn bitxor(self, rhs: Self) -> Self {
Varying::wrap(self.backend, self.backend.bit_xor(self.v, rhs.v))
}
}
impl<T: IntScalar, S: Backend<T>> Not for Varying<T, S> {
type Output = Varying<T, S>;
#[inline(always)]
fn not(self) -> Self {
Varying::wrap(self.backend, self.backend.bit_not(self.v))
}
}
#[derive(Clone, Copy)]
pub struct Mask<T: Scalar, S: Backend<T>> {
backend: S,
m: S::Mask,
_t: PhantomData<T>,
}
impl<T: Scalar, S: Backend<T>> Mask<T, S> {
#[inline(always)]
fn wrap(backend: S, m: S::Mask) -> Self {
Self {
backend,
m,
_t: PhantomData,
}
}
#[inline(always)]
pub fn raw(self) -> S::Mask {
self.m
}
#[inline(always)]
pub fn any(self) -> bool {
self.backend.any(self.m)
}
#[inline(always)]
pub fn all(self) -> bool {
self.backend.all(self.m)
}
#[inline(always)]
pub fn to_bitmask(self) -> u32 {
self.backend.mask_bitmask(self.m)
}
}
impl<T: Scalar, S: Backend<T>> BitAnd for Mask<T, S> {
type Output = Mask<T, S>;
#[inline(always)]
fn bitand(self, rhs: Self) -> Self {
Mask::wrap(self.backend, self.backend.mask_and(self.m, rhs.m))
}
}
impl<T: Scalar, S: Backend<T>> BitOr for Mask<T, S> {
type Output = Mask<T, S>;
#[inline(always)]
fn bitor(self, rhs: Self) -> Self {
Mask::wrap(self.backend, self.backend.mask_or(self.m, rhs.m))
}
}
impl<T: Scalar, S: Backend<T>> Not for Mask<T, S> {
type Output = Mask<T, S>;
#[inline(always)]
fn not(self) -> Self {
Mask::wrap(self.backend, self.backend.mask_not(self.m))
}
}
#[derive(Clone, Copy)]
pub struct VaryingU32<T: Scalar, S: Backend<T>> {
backend: S,
v: S::IVector,
_t: PhantomData<T>,
}
#[derive(Clone, Copy)]
pub struct VaryingI32<T: Scalar, S: Backend<T>> {
backend: S,
v: S::IVector,
_t: PhantomData<T>,
}
impl<T: Scalar, S: Backend<T>> VaryingU32<T, S> {
#[inline(always)]
fn wrap(backend: S, v: S::IVector) -> Self {
Self { backend, v, _t: PhantomData }
}
#[inline(always)]
pub fn raw(self) -> S::IVector {
self.v
}
#[inline(always)]
pub fn store(self, out: &mut [u32]) {
assert!(
out.len() == self.backend.lanes(),
"VaryingU32::store: slice length must equal lanes()",
);
self.backend.istore(self.v, out)
}
#[inline(always)]
pub fn as_i32(self) -> VaryingI32<T, S> {
VaryingI32 { backend: self.backend, v: self.v, _t: PhantomData }
}
#[inline(always)]
pub fn to_float_bits(self) -> Varying<T, S> {
Varying::wrap(self.backend, self.backend.from_bits(self.v))
}
#[inline(always)]
pub fn eq(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.ieq(self.v, o.v))
}
#[inline(always)]
pub fn ne(self, o: Self) -> Mask<T, S> {
!self.eq(o)
}
#[inline(always)]
pub fn lt(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.ilt_u(self.v, o.v))
}
#[inline(always)]
pub fn gt(self, o: Self) -> Mask<T, S> {
o.lt(self)
}
#[inline(always)]
pub fn le(self, o: Self) -> Mask<T, S> {
!o.lt(self)
}
#[inline(always)]
pub fn ge(self, o: Self) -> Mask<T, S> {
!self.lt(o)
}
#[inline(always)]
pub fn select(self, mask: Mask<T, S>, other: Self) -> Self {
Self::wrap(self.backend, self.backend.iselect(mask.m, self.v, other.v))
}
}
impl<T: Scalar, S: Backend<T>> VaryingI32<T, S> {
#[inline(always)]
pub fn raw(self) -> S::IVector {
self.v
}
#[inline(always)]
pub fn store(self, out: &mut [i32]) {
let n = self.backend.lanes();
assert!(out.len() == n, "VaryingI32::store: slice length must equal lanes()");
let mut buf = [0u32; crate::MAX_LANES];
self.backend.istore(self.v, &mut buf[..n]);
for (o, &b) in out.iter_mut().zip(&buf[..n]) {
*o = b as i32;
}
}
#[inline(always)]
pub fn as_u32(self) -> VaryingU32<T, S> {
VaryingU32 { backend: self.backend, v: self.v, _t: PhantomData }
}
#[inline(always)]
pub fn eq(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.ieq(self.v, o.v))
}
#[inline(always)]
pub fn ne(self, o: Self) -> Mask<T, S> {
!self.eq(o)
}
#[inline(always)]
pub fn lt(self, o: Self) -> Mask<T, S> {
Mask::wrap(self.backend, self.backend.ilt_s(self.v, o.v))
}
#[inline(always)]
pub fn gt(self, o: Self) -> Mask<T, S> {
o.lt(self)
}
#[inline(always)]
pub fn le(self, o: Self) -> Mask<T, S> {
!o.lt(self)
}
#[inline(always)]
pub fn ge(self, o: Self) -> Mask<T, S> {
!self.lt(o)
}
#[inline(always)]
pub fn select(self, mask: Mask<T, S>, other: Self) -> Self {
Self { backend: self.backend, v: self.backend.iselect(mask.m, self.v, other.v), _t: PhantomData }
}
}
macro_rules! int_binop {
($ty:ident, $trait:ident, $method:ident, $bk:ident) => {
impl<T: Scalar, S: Backend<T>> $trait for $ty<T, S> {
type Output = $ty<T, S>;
#[inline(always)]
fn $method(self, rhs: Self) -> Self {
Self { backend: self.backend, v: self.backend.$bk(self.v, rhs.v), _t: PhantomData }
}
}
};
}
int_binop!(VaryingU32, Add, add, iadd);
int_binop!(VaryingU32, Sub, sub, isub);
int_binop!(VaryingU32, Mul, mul, imul);
int_binop!(VaryingU32, BitAnd, bitand, iand);
int_binop!(VaryingU32, BitOr, bitor, ior);
int_binop!(VaryingU32, BitXor, bitxor, ixor);
int_binop!(VaryingI32, Add, add, iadd);
int_binop!(VaryingI32, Sub, sub, isub);
int_binop!(VaryingI32, Mul, mul, imul);
int_binop!(VaryingI32, BitAnd, bitand, iand);
int_binop!(VaryingI32, BitOr, bitor, ior);
int_binop!(VaryingI32, BitXor, bitxor, ixor);
impl<T: Scalar, S: Backend<T>> Not for VaryingU32<T, S> {
type Output = Self;
#[inline(always)]
fn not(self) -> Self {
Self { backend: self.backend, v: self.backend.inot(self.v), _t: PhantomData }
}
}
impl<T: Scalar, S: Backend<T>> Not for VaryingI32<T, S> {
type Output = Self;
#[inline(always)]
fn not(self) -> Self {
Self { backend: self.backend, v: self.backend.inot(self.v), _t: PhantomData }
}
}
impl<T: Scalar, S: Backend<T>> core::ops::Shl<u32> for VaryingU32<T, S> {
type Output = Self;
#[inline(always)]
fn shl(self, k: u32) -> Self {
Self { backend: self.backend, v: self.backend.ishl(self.v, k), _t: PhantomData }
}
}
impl<T: Scalar, S: Backend<T>> core::ops::Shr<u32> for VaryingU32<T, S> {
type Output = Self;
#[inline(always)]
fn shr(self, k: u32) -> Self {
Self { backend: self.backend, v: self.backend.ishr(self.v, k), _t: PhantomData }
}
}
impl<T: Scalar, S: Backend<T>> core::ops::Shl<u32> for VaryingI32<T, S> {
type Output = Self;
#[inline(always)]
fn shl(self, k: u32) -> Self {
Self { backend: self.backend, v: self.backend.ishl(self.v, k), _t: PhantomData }
}
}
impl<T: Scalar, S: Backend<T>> core::ops::Shr<u32> for VaryingI32<T, S> {
type Output = Self;
#[inline(always)]
fn shr(self, k: u32) -> Self {
Self { backend: self.backend, v: self.backend.ishr_arith(self.v, k), _t: PhantomData }
}
}