use core::mem::MaybeUninit;
use generic_array::{
ArrayLength, GenericArray,
sequence::GenericSequence,
typenum::{U1, U2, U3, U4, U5, U6, U7, U8, U16, U32, U64, U256, Unsigned},
};
use super::*;
pub type CompressRow = (GenericArray<u8, U8>, u8);
pub static COMPRESS8: GenericArray<CompressRow, U256> = build_table8();
pub trait CompressTable: Lanes {}
impl CompressTable for U1 {}
impl CompressTable for U2 {}
impl CompressTable for U3 {}
impl CompressTable for U4 {}
impl CompressTable for U5 {}
impl CompressTable for U6 {}
impl CompressTable for U7 {}
impl CompressTable for U8 {}
#[inline(always)]
pub fn widen_row<N: ArrayLength>(row: &GenericArray<u8, U8>) -> GenericArray<u32, N> {
let mut idxs: GenericArray<u32, N> = GenericArray::default();
let n = <N as Unsigned>::USIZE;
let mut i = 0;
while i < n {
idxs[i] = row[i] as u32;
i += 1;
}
idxs
}
const fn build_table8() -> GenericArray<CompressRow, U256> {
let lanes = 8;
let patterns = 256;
let mut table: GenericArray<CompressRow, U256> = unsafe { MaybeUninit::zeroed().assume_init() };
let rows = table.as_mut_slice();
let mut m = 0;
while m < patterns {
let row = rows[m].0.as_mut_slice();
let mut pos = 0;
let mut i = 0;
while i < lanes {
if (m >> i) & 1 == 1 {
row[pos] = i as u8;
pos += 1;
}
i += 1;
}
let count = pos as u8;
let mut i = 0;
while i < lanes {
if (m >> i) & 1 == 0 {
row[pos] = i as u8;
pos += 1;
}
i += 1;
}
rows[m].1 = count;
m += 1;
}
table
}
#[inline(always)]
pub fn compress_permute<R>(value: Storage<R>, mask: Storage<R::Mask>) -> Storage<R>
where
R: WidenIndexRegister<Lanes: CompressTable>,
{
unsafe { compress_permute8_raw::<R>(value, mask) }
}
#[inline(always)]
pub unsafe fn compress_permute8_raw<R: WidenIndexRegister>(value: Storage<R>, mask: Storage<R::Mask>) -> Storage<R> {
let bm = unsafe { <R::Mask as MaskRegister>::native_bitmask(mask).unwrap_unchecked() } as usize;
R::permutev_row(value, &unsafe { COMPRESS8.get_unchecked(bm) }.0)
}
#[inline(always)]
pub fn compress_permute_wide<R>(value: Storage<R>, mask: Storage<R::Mask>) -> Storage<R>
where
R: Register,
{
const {
assert!(
<R::Lanes as Unsigned>::USIZE % 8 == 0,
"compress_permute_wide requires a lane count that is a multiple of 8"
);
assert!(
<R::Lanes as Unsigned>::USIZE <= 64,
"compress_permute_wide requires <= 64 lanes (native_bitmask bound)"
);
}
let n = <R::Lanes as Unsigned>::USIZE;
let groups = n / 8;
let bm = unsafe { <R::Mask as MaskRegister>::native_bitmask(mask).unwrap_unchecked() };
let table8 = &COMPRESS8;
let mut counts = [0u8; 8];
let mut total = 0usize;
for group in 0..groups {
let bmg = ((bm >> (group * 8)) & 0xFF) as usize;
let cnt = unsafe { table8.get_unchecked(bmg) }.1;
counts[group] = cnt;
total += cnt as usize;
}
let mut g: GenericArray<u32, R::Lanes> = GenericArray::default();
let mut head = 0usize; let mut tail = total;
for group in 0..groups {
let base = group * 8;
let bmg = ((bm >> base) & 0xFF) as usize;
let cnt = counts[group] as usize;
let row = &unsafe { table8.get_unchecked(bmg) }.0;
for j in 0..8 {
let global = (base + row[j] as usize) as u32;
let pos = if j < cnt { head + j } else { tail + (j - cnt) };
unsafe { *g.get_unchecked_mut(pos) = global };
}
head += cnt;
tail += 8 - cnt;
}
R::permutev(value, g)
}
#[inline(always)]
pub fn compress_z_wide<R: Register>(value: Storage<R>, mask: Storage<R::Mask>) -> Storage<R> {
const {
assert!(
<R::Lanes as Unsigned>::USIZE % 8 == 0,
"compress_z_wide requires a lane count that is a multiple of 8"
);
assert!(
<R::Lanes as Unsigned>::USIZE <= 64,
"compress_z_wide requires <= 64 lanes (native_bitmask bound)"
);
}
let n = <R::Lanes as Unsigned>::USIZE;
let groups = n / 8;
let bm = unsafe { <R::Mask as MaskRegister>::native_bitmask(mask).unwrap_unchecked() };
let zeroed = R::zz(mask, value);
let mut g: GenericArray<u32, R::Lanes> = GenericArray::generate(|_| n as u32);
let mut start = 0usize;
for c in 0..groups {
let base = (c * 8) as u32;
let bmg = ((bm >> (c * 8)) & 0xFF) as usize;
let entry = unsafe { COMPRESS8.get_unchecked(bmg) };
let mut off = [0u32; 8];
let mut j = 0;
while j < 8 {
off[j] = entry.0[j] as u32 + base;
j += 1;
}
unsafe { core::ptr::copy_nonoverlapping(off.as_ptr(), g.as_mut_ptr().add(start), 8) };
start += entry.1 as usize;
}
R::swizzle(zeroed, R::EMPTY, g)
}
const fn build_merge_ctrl<TwoH: ArrayLength, Entries: ArrayLength>(
h: usize,
) -> GenericArray<GenericArray<u32, TwoH>, Entries> {
let two_h = <TwoH as Unsigned>::USIZE;
let entries = <Entries as Unsigned>::USIZE;
let mut t: GenericArray<GenericArray<u32, TwoH>, Entries> = unsafe { MaybeUninit::zeroed().assume_init() };
let rows = t.as_mut_slice();
let mut c = 0;
while c < entries {
let row = rows[c].as_mut_slice();
let mut k = 0;
while k < two_h {
row[k] = if k < c {
k as u32
} else {
let src = h + (k - c);
if src < two_h { src as u32 } else { two_h as u32 }
};
k += 1;
}
c += 1;
}
t
}
static MERGE_CTRL_8: GenericArray<GenericArray<u32, U16>, generic_array::typenum::U9> = build_merge_ctrl(8);
static MERGE_CTRL_16: GenericArray<GenericArray<u32, U32>, generic_array::typenum::U17> = build_merge_ctrl(16);
static MERGE_CTRL_32: GenericArray<GenericArray<u32, U64>, generic_array::typenum::U33> = build_merge_ctrl(32);
#[inline(always)]
fn compress_chunk_z<B: WidenIndexRegister<Lanes = U8>>(
chunk: Storage<B>,
mask: Storage<B::Mask>,
) -> (Storage<B>, usize) {
let packed = compress_permute::<B>(B::zz(mask, chunk), mask);
let bm = unsafe { <B::Mask as MaskRegister>::native_bitmask(mask).unwrap_unchecked() } as usize;
let count = unsafe { COMPRESS8.get_unchecked(bm) }.1 as usize;
(packed, count)
}
#[inline(always)]
pub fn compress_z_merge2<B>(chunks: [Storage<B>; 2], masks: [Storage<B::Mask>; 2]) -> [Storage<B>; 2]
where
B: WidenIndexRegister<Lanes = U8>,
crate::register::array::ArrayRegister<B, 2>:
Register<Lanes = U16, Storage = crate::register::array::ArrayRegister<B, 2>>,
{
use crate::register::array::ArrayRegister;
let (lo, count_lo) = compress_chunk_z::<B>(chunks[0], masks[0]);
let (hi, _) = compress_chunk_z::<B>(chunks[1], masks[1]);
let full = ArrayRegister::<B, 2>([lo, hi]);
let zero = ArrayRegister::<B, 2>([B::EMPTY, B::EMPTY]);
let ctrl = unsafe { MERGE_CTRL_8.get_unchecked(count_lo) }.clone();
let merged: ArrayRegister<B, 2> = <ArrayRegister<B, 2>>::swizzle(full, zero, ctrl);
merged.0
}
#[inline(always)]
pub fn compress_z_merge4<B>(chunks: [Storage<B>; 4], masks: [Storage<B::Mask>; 4]) -> [Storage<B>; 4]
where
B: WidenIndexRegister<Lanes = U8>,
crate::register::array::ArrayRegister<B, 2>:
Register<Lanes = U16, Storage = crate::register::array::ArrayRegister<B, 2>>,
crate::register::array::ArrayRegister<B, 4>:
Register<Lanes = U32, Storage = crate::register::array::ArrayRegister<B, 4>>,
{
use crate::register::array::ArrayRegister;
let (c0, n0) = compress_chunk_z::<B>(chunks[0], masks[0]);
let (c1, n1) = compress_chunk_z::<B>(chunks[1], masks[1]);
let (c2, n2) = compress_chunk_z::<B>(chunks[2], masks[2]);
let (c3, _) = compress_chunk_z::<B>(chunks[3], masks[3]);
let zero16 = ArrayRegister::<B, 2>([B::EMPTY; 2]);
let m01: ArrayRegister<B, 2> = <ArrayRegister<B, 2>>::swizzle(
ArrayRegister::<B, 2>([c0, c1]),
zero16,
unsafe { MERGE_CTRL_8.get_unchecked(n0) }.clone(),
);
let m23: ArrayRegister<B, 2> = <ArrayRegister<B, 2>>::swizzle(
ArrayRegister::<B, 2>([c2, c3]),
zero16,
unsafe { MERGE_CTRL_8.get_unchecked(n2) }.clone(),
);
let full = ArrayRegister::<B, 4>([m01.0[0], m01.0[1], m23.0[0], m23.0[1]]);
let zero32 = ArrayRegister::<B, 4>([B::EMPTY; 4]);
let result: ArrayRegister<B, 4> =
<ArrayRegister<B, 4>>::swizzle(full, zero32, unsafe { MERGE_CTRL_16.get_unchecked(n0 + n1) }.clone());
result.0
}
#[inline(always)]
pub fn compress_z_merge8<B>(chunks: [Storage<B>; 8], masks: [Storage<B::Mask>; 8]) -> [Storage<B>; 8]
where
B: WidenIndexRegister<Lanes = U8>,
crate::register::array::ArrayRegister<B, 2>:
Register<Lanes = U16, Storage = crate::register::array::ArrayRegister<B, 2>>,
crate::register::array::ArrayRegister<B, 4>:
Register<Lanes = U32, Storage = crate::register::array::ArrayRegister<B, 4>>,
crate::register::array::ArrayRegister<B, 8>:
Register<Lanes = U64, Storage = crate::register::array::ArrayRegister<B, 8>>,
{
use crate::register::array::ArrayRegister;
let (c0, n0) = compress_chunk_z::<B>(chunks[0], masks[0]);
let (c1, n1) = compress_chunk_z::<B>(chunks[1], masks[1]);
let (c2, n2) = compress_chunk_z::<B>(chunks[2], masks[2]);
let (c3, n3) = compress_chunk_z::<B>(chunks[3], masks[3]);
let (c4, n4) = compress_chunk_z::<B>(chunks[4], masks[4]);
let (c5, n5) = compress_chunk_z::<B>(chunks[5], masks[5]);
let (c6, n6) = compress_chunk_z::<B>(chunks[6], masks[6]);
let (c7, _) = compress_chunk_z::<B>(chunks[7], masks[7]);
let zero16 = ArrayRegister::<B, 2>([B::EMPTY; 2]);
let merge16 = |a: Storage<B>, b: Storage<B>, na: usize| -> ArrayRegister<B, 2> {
<ArrayRegister<B, 2>>::swizzle(
ArrayRegister::<B, 2>([a, b]),
zero16,
unsafe { MERGE_CTRL_8.get_unchecked(na) }.clone(),
)
};
let m01 = merge16(c0, c1, n0);
let m23 = merge16(c2, c3, n2);
let m45 = merge16(c4, c5, n4);
let m67 = merge16(c6, c7, n6);
let zero32 = ArrayRegister::<B, 4>([B::EMPTY; 4]);
let merge32 = |lo: ArrayRegister<B, 2>, hi: ArrayRegister<B, 2>, nlo: usize| -> ArrayRegister<B, 4> {
let full = ArrayRegister::<B, 4>([lo.0[0], lo.0[1], hi.0[0], hi.0[1]]);
<ArrayRegister<B, 4>>::swizzle(full, zero32, unsafe { MERGE_CTRL_16.get_unchecked(nlo) }.clone())
};
let m0123 = merge32(m01, m23, n0 + n1);
let m4567 = merge32(m45, m67, n4 + n5);
let full = ArrayRegister::<B, 8>([
m0123.0[0], m0123.0[1], m0123.0[2], m0123.0[3], m4567.0[0], m4567.0[1], m4567.0[2], m4567.0[3],
]);
let zero64 = ArrayRegister::<B, 8>([B::EMPTY; 8]);
let result: ArrayRegister<B, 8> = <ArrayRegister<B, 8>>::swizzle(
full,
zero64,
unsafe { MERGE_CTRL_32.get_unchecked(n0 + n1 + n2 + n3) }.clone(),
);
result.0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::register::array::ArrayRegister;
fn check<const N: usize>()
where
generic_array::typenum::Const<N>: generic_array::IntoArrayLength,
ArrayRegister<i32, N>: WidenIndexRegister<Element = i32, Lanes: CompressTable>,
{
type R<const N: usize> = ArrayRegister<i32, N>;
let mut data = [0i32; 16];
for i in 0..N {
data[i] = ((i + 1) * 10) as i32;
}
let value = <R<N>>::new(GenericArray::from_slice(&data[..N]).clone());
for bits in 0u32..(1 << N) {
let mut sel = [0i32; 16];
for lane in 0..N {
sel[lane] = ((bits >> lane) & 1) as i32;
}
let mask = <R<N>>::into_mask(<R<N>>::new(GenericArray::from_slice(&sel[..N]).clone()));
let mut expected = [0i32; 16];
let mut pos = 0;
for lane in 0..N {
if (bits >> lane) & 1 == 1 {
expected[pos] = data[lane];
pos += 1;
}
}
for lane in 0..N {
if (bits >> lane) & 1 == 0 {
expected[pos] = data[lane];
pos += 1;
}
}
let got = compress_permute::<R<N>>(value, mask);
let got = <R<N>>::as_slice(&got);
for lane in 0..N {
assert_eq!(got[lane], expected[lane], "N={N} bits={bits:b} lane={lane}");
}
}
}
#[test]
fn compress_permute_exhaustive() {
check::<2>();
check::<4>();
check::<8>();
}
fn check_wide<const N: usize>(patterns: impl Iterator<Item = u64>)
where
generic_array::typenum::Const<N>: generic_array::IntoArrayLength,
ArrayRegister<i32, N>: Register<Element = i32>,
{
type R<const N: usize> = ArrayRegister<i32, N>;
let mut data = [0i32; 64];
for i in 0..N {
data[i] = ((i + 1) * 10) as i32;
}
let value = <R<N>>::new(GenericArray::from_slice(&data[..N]).clone());
for bits in patterns {
let mut sel = [0i32; 64];
for lane in 0..N {
sel[lane] = ((bits >> lane) & 1) as i32;
}
let mask = <R<N>>::into_mask(<R<N>>::new(GenericArray::from_slice(&sel[..N]).clone()));
let mut expected = [0i32; 64];
let mut pos = 0;
for lane in 0..N {
if (bits >> lane) & 1 == 1 {
expected[pos] = data[lane];
pos += 1;
}
}
for lane in 0..N {
if (bits >> lane) & 1 == 0 {
expected[pos] = data[lane];
pos += 1;
}
}
let got = compress_permute_wide::<R<N>>(value, mask);
let got = <R<N>>::as_slice(&got);
for lane in 0..N {
assert_eq!(got[lane], expected[lane], "N={N} bits={bits:b} lane={lane}");
}
}
}
#[test]
fn compress_permute_wide_correct() {
check_wide::<8>(0..(1u64 << 8));
check_wide::<16>(0..(1u64 << 16));
let mut s = 0x9E3779B97F4A7C15u64;
let mut rng = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let structured = [
0u64,
u64::MAX,
0x5555_5555_5555_5555,
0xAAAA_AAAA_AAAA_AAAA,
0x0000_0000_FFFF_FFFF,
0xFFFF_FFFF_0000_0000,
0x00FF_00FF_00FF_00FF,
0x0101_0101_0101_0101,
0x8080_8080_8080_8080,
];
check_wide::<32>(structured.into_iter().chain((0..2000).map(move |_| rng())));
let mut s = 0xDEADBEEFCAFEBABEu64;
let mut rng = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
check_wide::<64>(structured.into_iter().chain((0..2000).map(move |_| rng())));
}
fn check_z_wide<const N: usize>(patterns: impl Iterator<Item = u64>)
where
generic_array::typenum::Const<N>: generic_array::IntoArrayLength,
ArrayRegister<i32, N>: Register<Element = i32>,
{
type R<const N: usize> = ArrayRegister<i32, N>;
let mut data = [0i32; 64];
for i in 0..N {
data[i] = ((i + 1) * 10) as i32;
}
let value = <R<N>>::new(GenericArray::from_slice(&data[..N]).clone());
for bits in patterns {
let mut sel = [0i32; 64];
for lane in 0..N {
sel[lane] = ((bits >> lane) & 1) as i32;
}
let mask = <R<N>>::into_mask(<R<N>>::new(GenericArray::from_slice(&sel[..N]).clone()));
let expected = zeroing_oracle::<N>(&data, bits);
let got = compress_z_wide::<R<N>>(value, mask);
let got = <R<N>>::as_slice(&got);
for lane in 0..N {
assert_eq!(got[lane], expected[lane], "N={N} bits={bits:b} lane={lane}");
}
}
}
#[test]
fn compress_z_wide_correct() {
check_z_wide::<8>(0..256);
check_z_wide::<16>(0..(1 << 16));
let mut s = 0x243F_6A88_85A3_08D3u64;
let mut rng = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let structured = [
0u64,
u64::MAX,
0x5555_5555_5555_5555,
0xAAAA_AAAA_AAAA_AAAA,
0x0000_0000_FFFF_FFFF,
];
check_z_wide::<32>(structured.into_iter().chain((0..5000).map(|_| rng() & 0xFFFF_FFFF)));
check_z_wide::<64>(structured.into_iter().chain((0..5000).map(|_| rng())));
}
#[test]
fn compress_z_merge2_correct() {
type B = ArrayRegister<i32, 8>;
let mut data = [0i32; 16];
for i in 0..16 {
data[i] = ((i + 1) * 10) as i32;
}
let v0 = <B>::new(GenericArray::from_slice(&data[0..8]).clone());
let v1 = <B>::new(GenericArray::from_slice(&data[8..16]).clone());
for bits in 0u32..(1 << 16) {
let mut sel = [0i32; 16];
for lane in 0..16 {
sel[lane] = ((bits >> lane) & 1) as i32;
}
let m0 = <B>::into_mask(<B>::new(GenericArray::from_slice(&sel[0..8]).clone()));
let m1 = <B>::into_mask(<B>::new(GenericArray::from_slice(&sel[8..16]).clone()));
let mut expected = [0i32; 16];
let mut pos = 0;
for lane in 0..16 {
if (bits >> lane) & 1 == 1 {
expected[pos] = data[lane];
pos += 1;
}
}
let got = compress_z_merge2::<B>([v0, v1], [m0, m1]);
let g0 = <B>::as_slice(&got[0]);
let g1 = <B>::as_slice(&got[1]);
for lane in 0..16 {
let val = if lane < 8 { g0[lane] } else { g1[lane - 8] };
assert_eq!(val, expected[lane], "bits={bits:b} lane={lane}");
}
}
}
fn zeroing_oracle<const LANES: usize>(data: &[i32], bits: u64) -> [i32; 64] {
let mut expected = [0i32; 64];
let mut pos = 0;
for lane in 0..LANES {
if (bits >> lane) & 1 == 1 {
expected[pos] = data[lane];
pos += 1;
}
}
expected
}
#[test]
fn compress_z_merge4_correct() {
type B = ArrayRegister<i32, 8>;
let mut data = [0i32; 32];
for i in 0..32 {
data[i] = ((i + 1) * 10) as i32;
}
let v: [Storage<B>; 4] =
core::array::from_fn(|c| <B>::new(GenericArray::from_slice(&data[c * 8..c * 8 + 8]).clone()));
let mut s = 0x1234_5678_9ABC_DEF0u64;
let mut rng = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let structured = [
0u64,
u32::MAX as u64,
0x5555_5555,
0xAAAA_AAAA,
0x0000_FFFF,
0xFFFF_0000,
0x00FF_FF00,
];
for bits in structured.into_iter().chain((0..5000).map(|_| rng() & 0xFFFF_FFFF)) {
let m: [Storage<<B as CoreRegister>::Mask>; 4] = core::array::from_fn(|c| {
let sel: [i32; 8] = core::array::from_fn(|l| ((bits >> (c * 8 + l)) & 1) as i32);
<B>::into_mask(<B>::new(GenericArray::from_slice(&sel).clone()))
});
let expected = zeroing_oracle::<32>(&data, bits);
let got = compress_z_merge4::<B>(v, m);
for lane in 0..32 {
assert_eq!(
<B>::as_slice(&got[lane / 8])[lane % 8],
expected[lane],
"bits={bits:b} lane={lane}"
);
}
}
}
#[test]
fn compress_z_merge8_correct() {
type B = ArrayRegister<i32, 8>;
let mut data = [0i32; 64];
for i in 0..64 {
data[i] = ((i + 1) * 10) as i32;
}
let v: [Storage<B>; 8] =
core::array::from_fn(|c| <B>::new(GenericArray::from_slice(&data[c * 8..c * 8 + 8]).clone()));
let mut s = 0x0F1E_2D3C_4B5A_6978u64;
let mut rng = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let structured = [
0u64,
u64::MAX,
0x5555_5555_5555_5555,
0xAAAA_AAAA_AAAA_AAAA,
0x0000_0000_FFFF_FFFF,
0xFFFF_FFFF_0000_0000,
];
for bits in structured.into_iter().chain((0..5000).map(|_| rng())) {
let m: [Storage<<B as CoreRegister>::Mask>; 8] = core::array::from_fn(|c| {
let sel: [i32; 8] = core::array::from_fn(|l| ((bits >> (c * 8 + l)) & 1) as i32);
<B>::into_mask(<B>::new(GenericArray::from_slice(&sel).clone()))
});
let expected = zeroing_oracle::<64>(&data, bits);
let got = compress_z_merge8::<B>(v, m);
for lane in 0..64 {
assert_eq!(
<B>::as_slice(&got[lane / 8])[lane % 8],
expected[lane],
"bits={bits:b} lane={lane}"
);
}
}
}
}
pub fn compress_default<R: Register>(value: Storage<R>, mask: Storage<R::Mask>) -> Storage<R> {
let n = <R::Lanes as Unsigned>::USIZE;
let src = R::as_slice(&value);
let mut result = value;
let dst = R::as_mut_slice(&mut result);
let mut pos = 0;
for i in 0..n {
if <R::Mask as MaskRegister>::test(mask, i) {
dst[pos] = src[i];
pos += 1;
}
}
for i in 0..n {
if !<R::Mask as MaskRegister>::test(mask, i) {
dst[pos] = src[i];
pos += 1;
}
}
result
}
pub fn compress_z_default<R: Register>(value: Storage<R>, mask: Storage<R::Mask>) -> Storage<R> {
let n = <R::Lanes as Unsigned>::USIZE;
let src = R::as_slice(&value);
let mut result = R::EMPTY;
let dst = R::as_mut_slice(&mut result);
let mut pos = 0;
for i in 0..n {
if <R::Mask as MaskRegister>::test(mask, i) {
dst[pos] = src[i];
pos += 1;
}
}
result
}
pub fn compress_m_default<R: Register>(src: Storage<R>, mask: Storage<R::Mask>, value: Storage<R>) -> Storage<R> {
let n = <R::Lanes as Unsigned>::USIZE;
let val = R::as_slice(&value);
let mut result = src;
let dst = R::as_mut_slice(&mut result);
let mut pos = 0;
for i in 0..n {
if <R::Mask as MaskRegister>::test(mask, i) {
dst[pos] = val[i];
pos += 1;
}
}
result
}