use core::arch::aarch64::{
uint16x8_t, uint32x4_t, uint8x16_t, uint8x16x2_t, vandq_u8, vcgtq_u8, vcltq_u8, vget_high_u8,
vget_lane_u64, vget_low_u16, vget_low_u8, vld1q_u16, vld1q_u8, vmaxvq_u8, vmovl_u8,
vmull_high_u16, vmull_u16, vmulq_u16, vpaddq_u32, vqaddq_u16, vqtbl2q_u8, vreinterpret_u64_u8,
vreinterpretq_u16_u8, vshrn_n_u16, vst1q_u16, vst1q_u32, vsubq_u8, vuzp1q_u16, vuzp2q_u16,
};
use crate::TimestampError;
#[target_feature(enable = "neon")]
pub(super) unsafe fn decode_seconds(ascii: &mut &[u8]) -> Result<i64, TimestampError> {
const LOWER_BOUND: [u8; 16] = [
b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'T',
b':',
];
const UPPER_BOUND: [u8; 16] = [
9, 9, 9, 9, 1, 9, 3, 9, 2, 9, 5, 9, 5, 9, 0, 0,
];
const MULT_10: [u8; 16] = [10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 0, 0];
const SHUFFLE: [u8; 16] = [
0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 30, 31, 10, 29, ];
unsafe {
if ascii.len() < 20 {
return Err(TimestampError::InvalidFormat);
}
let vec0 = vld1q_u8(ascii.as_ptr());
let vec1 = vld1q_u8(ascii.as_ptr().add(3));
let table = uint8x16x2_t(vec0, vec1);
let shuffle = vld1q_u8(SHUFFLE.as_ptr());
let mut tmp = vqtbl2q_u8(table, shuffle);
let lower_bound = vld1q_u8(LOWER_BOUND.as_ptr());
let upper_bound = vld1q_u8(UPPER_BOUND.as_ptr());
tmp = vsubq_u8(tmp, lower_bound);
let higher = vcgtq_u8(tmp, upper_bound);
if vmaxvq_u8(higher) != 0 {
return Err(TimestampError::InvalidFormat);
}
let mult_10 = vld1q_u8(MULT_10.as_ptr());
let tmp = maddubs_neon(tmp, mult_10);
let mut res: [u8; 16] = [0u8; 16];
vst1q_u16(res.as_mut_ptr().cast(), tmp);
let year = res[0] as i32 * 100 + res[2] as i32;
*ascii = &ascii[19..];
Ok(crate::jsondec_unixtime(
year,
res[4] as i32,
res[6] as i32,
res[8] as i32,
res[10] as i32,
res[12] as i32,
))
}
}
#[target_feature(enable = "neon")]
pub(super) unsafe fn decode_nanos(ascii: &mut &[u8]) -> Result<i32, TimestampError> {
if ascii[0] != b'.' {
return Ok(0);
}
const ASCII_ZERO: [u8; 16] = [b'0'; 16];
const DIGIT_MAX: [u8; 16] = [10; 16];
const THREE_DIGITS: u64 = 0x0000_0000_0000_FFF0;
const SIX_DIGITS: u64 = 0x0000_0000_FFF0_FFF0;
const NINE_DIGITS: u64 = 0x0000_FFF0_FFF0_FFF0;
const MULT_100_10: [u8; 16] = [0, 100, 10, 1, 0, 100, 10, 1, 0, 100, 10, 1, 0, 100, 10, 1];
const MULT_1000: [u16; 8] = [1000, 1000, 1000, 1000, 1, 1, 0, 0];
unsafe {
let mut tmp = match ascii.len() {
5 => {
let t: [u8; 16] = [
0, ascii[1], ascii[2], ascii[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
vld1q_u8(t.as_ptr())
}
8 => {
let t: [u8; 16] = [
0, ascii[1], ascii[2], ascii[3], 0, ascii[4], ascii[5], ascii[6], 0, 0, 0, 0,
0, 0, 0, 0,
];
vld1q_u8(t.as_ptr())
}
11..=16 => {
let t: [u8; 16] = [
0, ascii[1], ascii[2], ascii[3], 0, ascii[4], ascii[5], ascii[6], 0, ascii[7],
ascii[8], ascii[9], 0, 0, 0, 0,
];
vld1q_u8(t.as_ptr())
}
_ => {
return Err(TimestampError::InvalidFormat);
}
};
let ascii_zero = vld1q_u8(ASCII_ZERO.as_ptr());
let digit_max = vld1q_u8(DIGIT_MAX.as_ptr());
tmp = vsubq_u8(tmp, ascii_zero);
let valid_digits = vcltq_u8(tmp, digit_max);
let mask = neon_nibblemask(valid_digits);
let offset = if mask | NINE_DIGITS == mask {
10
} else if mask | SIX_DIGITS == mask {
7
} else if mask | THREE_DIGITS == mask {
4
} else {
return Err(TimestampError::InvalidFormat);
};
tmp = vandq_u8(tmp, valid_digits);
let tmp = maddubs_neon(tmp, vld1q_u8(MULT_100_10.as_ptr()));
let tmp = madd_neon(tmp, vld1q_u16(MULT_1000.as_ptr()));
let mut out: [i32; 4] = [0; 4];
vst1q_u32(out.as_mut_ptr().cast(), tmp);
*ascii = &ascii[offset..];
Ok(out[0] * 1000 + out[1] + out[2])
}
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn maddubs_neon(a: uint8x16_t, b: uint8x16_t) -> uint16x8_t {
{
let tl = vmulq_u16(vmovl_u8(vget_low_u8(a)), vmovl_u8(vget_low_u8(b)));
let th = vmulq_u16(vmovl_u8(vget_high_u8(a)), vmovl_u8(vget_high_u8(b)));
vqaddq_u16(vuzp1q_u16(tl, th), vuzp2q_u16(tl, th))
}
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn madd_neon(a: uint16x8_t, b: uint16x8_t) -> uint32x4_t {
{
let low = vmull_u16(vget_low_u16(a), vget_low_u16(b));
let high = vmull_high_u16(a, b);
vpaddq_u32(low, high)
}
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn neon_nibblemask(input: uint8x16_t) -> u64 {
{
let narrowed = vshrn_n_u16::<4>(vreinterpretq_u16_u8(input));
vget_lane_u64::<0>(vreinterpret_u64_u8(narrowed))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_seconds() {
let s = "2026-02-25T14:30:00Z";
let input = &mut s.as_bytes();
assert_eq!(unsafe { decode_seconds(input).unwrap() }, 1772029800);
assert_eq!(input, b"Z");
}
#[test]
fn test_decode_seconds_invalid_chars() {
let s = "20/6-02-25T14:30:00Z";
let input = &mut s.as_bytes();
assert!(unsafe { decode_seconds(input).is_err() });
let s = "20:6-02-25T14:30:00Z";
let input = &mut s.as_bytes();
assert!(unsafe { decode_seconds(input).is_err() });
}
#[test]
fn test_decode_nanos() {
let s = ".987654321Z";
let input = &mut s.as_bytes();
assert_eq!(unsafe { decode_nanos(input).unwrap() }, 987654321);
assert_eq!(input, b"Z");
let s = ".987654+00:00";
let input = &mut s.as_bytes();
assert_eq!(unsafe { decode_nanos(input).unwrap() }, 987654000);
assert_eq!(input, b"+00:00");
}
#[test]
fn test_decode_nanos_invalid_chars() {
let s = ".98/654321Z";
let input = &mut s.as_bytes();
assert!(unsafe { decode_nanos(input).is_err() });
let s = ".98:654321Z";
let input = &mut s.as_bytes();
assert!(unsafe { decode_nanos(input).is_err() });
}
#[test]
fn test_decode_nanos_invalid_length() {
let s = ".12Z";
let input = &mut s.as_bytes();
assert!(unsafe { decode_nanos(input).is_err() });
let s = ".12345Z";
let input = &mut s.as_bytes();
assert!(unsafe { decode_nanos(input).is_err() });
}
}