pub(crate) fn swap_rb(src: &[u8], dst: &mut [u8]) -> bool {
if src.len() != dst.len() || src.len() % 4 != 0 || !has_ssse3() {
return false;
}
unsafe { swap_rb_ssse3(src, dst) };
true
}
#[target_feature(enable = "ssse3")]
unsafe fn swap_rb_ssse3(src: &[u8], dst: &mut [u8]) {
use std::arch::x86_64::*;
let n = src.len();
let sp = src.as_ptr();
let dp = dst.as_mut_ptr();
let sel = _mm_setr_epi8(2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15);
let mut i = 0usize;
while i + 16 <= n {
let v = _mm_loadu_si128(sp.add(i) as *const __m128i);
_mm_storeu_si128(dp.add(i) as *mut __m128i, _mm_shuffle_epi8(v, sel));
i += 16;
}
while i < n {
let p = [*sp.add(i + 2), *sp.add(i + 1), *sp.add(i), *sp.add(i + 3)];
std::ptr::copy_nonoverlapping(p.as_ptr(), dp.add(i), 4);
i += 4;
}
}
#[inline]
pub(crate) fn has_ssse3() -> bool {
use std::sync::atomic::{AtomicU8, Ordering};
static F: AtomicU8 = AtomicU8::new(0);
match F.load(Ordering::Relaxed) {
0 => {
let v = if std::is_x86_feature_detected!("ssse3") { 2 } else { 1 };
F.store(v, Ordering::Relaxed);
v == 2
}
v => v == 2,
}
}
#[cfg(test)]
mod tests {
fn swap_rb_scalar(src: &[u8], dst: &mut [u8]) {
for (s, d) in src.chunks_exact(4).zip(dst.chunks_exact_mut(4)) {
d.copy_from_slice(&[s[2], s[1], s[0], s[3]]);
}
}
#[test]
fn swap_rb_matches_scalar() {
if !super::has_ssse3() {
eprintln!("SSSE3 not available; skipping");
return;
}
let mut state = 0x5111_22ab_c0ff_ee01u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let px = (next() % 68) as usize;
let mut src = vec![0u8; px * 4];
for b in src.iter_mut() {
*b = next() as u8;
}
let mut fast = vec![0u8; px * 4];
let mut slow = vec![0u8; px * 4];
assert!(super::swap_rb(&src, &mut fast), "dispatch declined, case {case}");
swap_rb_scalar(&src, &mut slow);
assert_eq!(fast, slow, "case {case}, {px} px");
}
let mut state2 = 0x9e37_79b9_7f4a_7c15u64;
let mut src = vec![0u8; 512 * 512 * 4];
for b in src.iter_mut() {
state2 ^= state2 << 13;
state2 ^= state2 >> 7;
state2 ^= state2 << 17;
*b = state2 as u8;
}
let mut fast = vec![0u8; src.len()];
let mut slow = vec![0u8; src.len()];
assert!(super::swap_rb(&src, &mut fast));
swap_rb_scalar(&src, &mut slow);
assert_eq!(fast, slow);
let mut twice = vec![0u8; src.len()];
assert!(super::swap_rb(&fast, &mut twice));
assert_eq!(twice, src);
}
#[test]
#[ignore]
fn probe_swap_rb_ab() {
assert!(super::has_ssse3(), "SSSE3 required for the A/B");
const PX: usize = 1024 * 1024;
let mut state = 0xdead_beef_cafe_f00du64;
let mut src = vec![0u8; PX * 4];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
let mut out = vec![0u8; PX * 4];
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..31 {
let t = std::time::Instant::now();
let sink = f();
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, 0); best = best.min(dt);
}
best
};
let scalar_ns = best(&mut || {
swap_rb_scalar(&src, &mut out);
out[123] as u64 + 1
});
let simd_ns = best(&mut || {
assert!(super::swap_rb(&src, &mut out));
out[123] as u64 + 1
});
eprintln!(
"swap_rb 1024x1024: scalar {:.3} ns/px, pshufb {:.3} ns/px, ratio {:.2}x",
scalar_ns as f64 / PX as f64,
simd_ns as f64 / PX as f64,
scalar_ns as f64 / simd_ns as f64,
);
}
}