use core::arch::wasm32::*;
use crate::alphabet::{Alphabet, ODD_VALUE};
const UNROLL: usize = 4;
pub(crate) fn encode_bulk<A: Alphabet>(input: &[u8], out: &mut [u8], groups: usize) -> usize {
if groups < 4 || groups * 3 > input.len() || groups * 4 > out.len() {
return 0;
}
unsafe {
let table = v128_load(A::SPELLING.as_ptr().cast());
let source = input.as_ptr();
let destination = out.as_mut_ptr();
let mut done = encode_run::<UNROLL>(table, source, destination, input, groups, 0);
while groups - done >= 4 {
let slack = input.len() - done * 3 >= 16;
encode_block(
table,
source.add(done * 3),
destination.add(done * 4),
slack,
);
done += 4;
}
if done < groups {
let start = groups - 4;
let slack = input.len() - start * 3 >= 16;
encode_block(
table,
source.add(start * 3),
destination.add(start * 4),
slack,
);
}
groups
}
}
#[inline(always)]
unsafe fn encode_run<const UNROLL: usize>(
table: v128,
source: *const u8,
destination: *mut u8,
input: &[u8],
groups: usize,
mut done: usize,
) -> usize {
unsafe {
while groups - done >= UNROLL * 4 && input.len() - done * 3 >= (UNROLL - 1) * 12 + 16 {
let base = source.add(done * 3);
let mut raw = [u8x16_splat(0); UNROLL];
for (step, slot) in raw.iter_mut().enumerate() {
*slot = v128_load(base.add(step * 12).cast());
}
let target = destination.add(done * 4);
for (step, held) in raw.into_iter().enumerate() {
v128_store(target.add(step * 16).cast(), encode_vector(table, held));
}
done += UNROLL * 4;
}
done
}
}
#[inline(always)]
unsafe fn encode_block(table: v128, source: *const u8, destination: *mut u8, slack: bool) {
unsafe {
let raw = if slack {
v128_load(source.cast())
} else {
v128_load32_lane::<2>(v128_load64_zero(source.cast()), source.add(8).cast())
};
v128_store(destination.cast(), encode_vector(table, raw));
}
}
#[inline(always)]
fn encode_vector(table: v128, raw: v128) -> v128 {
let gathered = i8x16_shuffle::<1, 0, 2, 1, 4, 3, 5, 4, 7, 6, 8, 7, 10, 9, 11, 10>(raw, raw);
let first = u16x8_shr(v128_and(gathered, i32x4_splat(0x0000_FC00)), 10);
let second = i16x8_shl(v128_and(gathered, i32x4_splat(0x0000_03F0)), 4);
let third = u16x8_shr(v128_and(gathered, i32x4_splat(0x0FC0_0000)), 6);
let fourth = i16x8_shl(v128_and(gathered, i32x4_splat(0x003F_0000)), 8);
let values = v128_or(v128_or(first, second), v128_or(third, fourth));
let index = i8x16_sub(
u8x16_sub_sat(values, u8x16_splat(51)),
i8x16_gt(values, i8x16_splat(25)),
);
i8x16_add(values, u8x16_swizzle(table, index))
}
pub(crate) fn decode_bulk<A: Alphabet>(input: &[u8], out: &mut [u8], quads: usize) -> usize {
if quads < 4 || quads * 4 > input.len() || quads * 3 > out.len() {
return 0;
}
unsafe {
let tables = Tables::load::<A>();
let source = input.as_ptr();
let destination = out.as_mut_ptr();
let mut done = 0;
while quads - done >= 12 {
let base = source.add(done * 4);
let front = v128_load(base.cast());
let back = v128_load(base.add(16).cast());
let target = destination.add(done * 3);
let (first, bad_first) = decode_vector(&tables, front);
let (second, bad_second) = decode_vector(&tables, back);
v128_store(target.cast(), first);
v128_store(target.add(12).cast(), second);
if v128_any_true(v128_or(bad_first, bad_second)) {
break;
}
done += 8;
}
while quads - done >= 4 {
let spare = quads - done >= 8;
let bad = decode_block(
&tables,
source.add(done * 4),
destination.add(done * 3),
spare,
);
if v128_any_true(bad) {
return done;
}
done += 4;
}
if done < quads {
let start = quads - 4;
let bad = decode_block(
&tables,
source.add(start * 4),
destination.add(start * 3),
false,
);
if !v128_any_true(bad) {
done = quads;
}
}
done
}
}
struct Tables {
low: v128,
high: v128,
roll: v128,
odd: v128,
}
impl Tables {
#[inline(always)]
unsafe fn load<A: Alphabet>() -> Self {
unsafe {
Tables {
low: v128_load(A::LUT_LO.as_ptr().cast()),
high: v128_load(A::LUT_HI.as_ptr().cast()),
roll: v128_load(A::LUT_ROLL.as_ptr().cast()),
odd: u8x16_splat(A::ODD_CHAR),
}
}
}
}
#[inline(always)]
unsafe fn decode_block(
tables: &Tables,
source: *const u8,
destination: *mut u8,
spare: bool,
) -> v128 {
unsafe {
let (packed, bad) = decode_vector(tables, v128_load(source.cast()));
if spare {
v128_store(destination.cast(), packed);
} else {
v128_store64_lane::<0>(packed, destination.cast());
v128_store32_lane::<2>(packed, destination.add(8).cast());
}
bad
}
}
#[inline(always)]
fn decode_vector(tables: &Tables, chars: v128) -> (v128, v128) {
let high_nibble = v128_and(u32x4_shr(chars, 4), u8x16_splat(0x0F));
let low_nibble = v128_and(chars, u8x16_splat(0x0F));
let bad = v128_and(
u8x16_swizzle(tables.high, high_nibble),
u8x16_swizzle(tables.low, low_nibble),
);
let rolled = i8x16_add(chars, u8x16_swizzle(tables.roll, high_nibble));
let values = v128_bitselect(u8x16_splat(ODD_VALUE), rolled, i8x16_eq(chars, tables.odd));
let paired = v128_or(
i16x8_shl(v128_and(values, u16x8_splat(0x00FF)), 6),
u16x8_shr(values, 8),
);
let merged = i32x4_dot_i16x8(paired, i32x4_splat(0x0001_1000));
let packed =
i8x16_shuffle::<2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, 12, 13, 14, 15>(merged, merged);
(packed, bad)
}