packages feed

ppad-base16-0.3.0: cbits/base16_arm.c

#include <stddef.h>
#include <stdint.h>

#if defined(__aarch64__)

#include <arm_neon.h>

/* lowercase ASCII hex character for each nibble value 0..15 */
static const uint8_t hex_lut[16] = {
    '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
};

/*
 * Encode 'l' input bytes at 'src' into 2*l ASCII hex bytes at 'dst'.
 *
 * NEON kernel processes 16 input bytes per iteration:
 *   - vshrq_n_u8 / vandq_u8 split the high and low nibbles
 *   - vqtbl1q_u8 looks up each nibble in 'hex_lut' (the tbl instr)
 *   - vst2q_u8 stores the two char vectors interleaved, producing
 *     [h0 l0 h1 l1 ... h15 l15] which is exactly the hex output
 *
 * A scalar tail finishes the final (l mod 16) bytes.
 */
void base16_encode_arm(const uint8_t *src, uint8_t *dst, size_t l) {
    uint8x16_t lut     = vld1q_u8(hex_lut);
    uint8x16_t mask_lo = vdupq_n_u8(0x0f);
    size_t i = 0;

    for (; i + 16 <= l; i += 16) {
        uint8x16_t in     = vld1q_u8(src + i);
        uint8x16_t hi     = vshrq_n_u8(in, 4);
        uint8x16_t lo     = vandq_u8(in, mask_lo);
        uint8x16x2_t pair = { { vqtbl1q_u8(lut, hi),
                                vqtbl1q_u8(lut, lo) } };
        vst2q_u8(dst + 2 * i, pair);
    }

    for (; i < l; i++) {
        uint8_t b = src[i];
        dst[2 * i]     = hex_lut[b >> 4];
        dst[2 * i + 1] = hex_lut[b & 0x0f];
    }
}

/*
 * Convert 16 ASCII hex chars to nibble values (0..15) in 'nib'.
 * Each lane of 'bad' is set to 0xff if the corresponding input is
 * not a valid hex digit ('0'..'9', 'a'..'f', 'A'..'F'), 0x00 if it
 * is.  Case-insensitive.
 */
static inline void ascii_to_nibble(uint8x16_t c,
                                   uint8x16_t *nib,
                                   uint8x16_t *bad) {
    uint8x16_t is_digit = vandq_u8(vcgeq_u8(c, vdupq_n_u8('0')),
                                    vcleq_u8(c, vdupq_n_u8('9')));
    uint8x16_t is_lower = vandq_u8(vcgeq_u8(c, vdupq_n_u8('a')),
                                    vcleq_u8(c, vdupq_n_u8('f')));
    uint8x16_t is_upper = vandq_u8(vcgeq_u8(c, vdupq_n_u8('A')),
                                    vcleq_u8(c, vdupq_n_u8('F')));

    /* offset to subtract from c: '0' (0x30), 'a'-10 (0x57), 'A'-10
     * (0x37); zero in lanes that aren't valid hex (the resulting
     * nibble in those lanes is garbage but 'bad' flags them).      */
    uint8x16_t off = vorrq_u8(
        vandq_u8(is_digit, vdupq_n_u8(0x30)),
        vorrq_u8(
            vandq_u8(is_lower, vdupq_n_u8(0x57)),
            vandq_u8(is_upper, vdupq_n_u8(0x37))));

    *nib = vsubq_u8(c, off);
    *bad = vmvnq_u8(vorrq_u8(is_digit, vorrq_u8(is_lower, is_upper)));
}

static inline uint8_t scalar_nib(uint8_t c) {
    if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
    if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 10);
    if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 10);
    return 0x80;  /* invalid sentinel */
}

/*
 * Decode 2*outlen hex chars at 'src' into 'outlen' bytes at 'dst'.
 * Returns 1 on success, 0 if any invalid hex char was seen (in which
 * case the contents of 'dst' are unspecified).
 *
 * NEON kernel processes 32 input chars per iteration:
 *   - vld2q_u8 deinterleaves into a vector of high-nibble chars and a
 *     vector of low-nibble chars
 *   - ascii_to_nibble validates and converts each char to its nibble
 *   - vshlq_n_u8 + vorrq_u8 packs nibble pairs into output bytes
 *   - the per-iteration 'bad' masks are OR-accumulated; we reduce
 *     once at the end with vmaxvq_u8
 *
 * A scalar tail finishes the final (outlen mod 16) output bytes.
 */
int base16_decode_arm(const uint8_t *src, uint8_t *dst, size_t outlen) {
    uint8x16_t bad = vdupq_n_u8(0);
    size_t i = 0;

    for (; i + 16 <= outlen; i += 16) {
        uint8x16x2_t pair = vld2q_u8(src + 2 * i);
        uint8x16_t nib_hi, nib_lo, bad_hi, bad_lo;
        ascii_to_nibble(pair.val[0], &nib_hi, &bad_hi);
        ascii_to_nibble(pair.val[1], &nib_lo, &bad_lo);
        uint8x16_t byte = vorrq_u8(vshlq_n_u8(nib_hi, 4), nib_lo);
        vst1q_u8(dst + i, byte);
        bad = vorrq_u8(bad, vorrq_u8(bad_hi, bad_lo));
    }

    uint8_t tail_bad = 0;
    for (; i < outlen; i++) {
        uint8_t n0 = scalar_nib(src[2 * i]);
        uint8_t n1 = scalar_nib(src[2 * i + 1]);
        tail_bad |= (n0 | n1) & 0x80;
        dst[i] = (uint8_t)((n0 << 4) | (n1 & 0x0f));
    }

    return (vmaxvq_u8(bad) == 0) && (tail_bad == 0);
}

int base16_arm_available(void) {
    return 1;
}

#else

/* stubs for non-aarch64 builds; never reached because dispatch is
 * gated on 'base16_arm_available' returning 0                     */

void base16_encode_arm(const uint8_t *src, uint8_t *dst, size_t l) {
    (void)src; (void)dst; (void)l;
}

int base16_decode_arm(const uint8_t *src, uint8_t *dst, size_t outlen) {
    (void)src; (void)dst; (void)outlen;
    return 0;
}

int base16_arm_available(void) {
    return 0;
}

#endif