packages feed

base32-1.0: cbits/base32_simd.c

/* Copyright (c) 2026 Emily Pillmore. BSD-3-Clause; see LICENSE. */
#include "base32_simd.h"
#include <string.h>

/* Each lane encodes ten bytes or decodes sixteen. All loads/stores are
 * bounded; invalid decode blocks are not committed. Target attributes keep
 * optional x86 instructions out of the baseline dispatcher. Force macros
 * only cap backend selection; they never bypass hardware/OS checks. */
#if !defined(HS_BASE32_SIMD_FORCE_SCALAR)
#if defined(__aarch64__) && defined(__ARM_NEON)
#define HS_BASE32_NEON
#include <arm_neon.h>
#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
#define HS_BASE32_X86
#include <immintrin.h>
#endif
#endif

#if defined(HS_BASE32_NEON) || defined(HS_BASE32_X86)
static const uint8_t enc_a[16] = {
    0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 6, 7, 8, 8, 9
};
static const uint8_t enc_b[16] = {
    255, 1, 255, 2, 3, 255, 4, 255, 255, 6, 255, 7, 8, 255, 9, 255
};
static const uint8_t dec_a[16] = {
    0, 1, 3, 4, 6, 8, 9, 11, 12, 14, 255, 255, 255, 255, 255, 255
};
static const uint8_t dec_b[16] = {
    1, 2, 4, 5, 7, 9, 10, 12, 13, 15, 255, 255, 255, 255, 255, 255
};
static const uint8_t dec_c[16] = {
    255, 3, 255, 6, 255, 255, 11, 255, 14, 255, 255, 255, 255, 255, 255, 255
};
#endif

#if defined(HS_BASE32_NEON)

static uint8x16_t load10(const uint8_t *src)
{
    uint8_t bytes[16] = {0};
    memcpy(bytes, src, 10);
    return vld1q_u8(bytes);
}

static void store10(uint8_t *dst, uint8x16_t value)
{
    uint8_t bytes[16];
    vst1q_u8(bytes, value);
    memcpy(dst, bytes, 10);
}

static size_t neon_encode(const uint8_t *src, size_t len, uint8_t *dst, int hex)
{
    static const int8_t shifts_a[16] = {
        -3,2,-1,4,1,-2,3,0, -3,2,-1,4,1,-2,3,0
    };
    static const int8_t shifts_b[16] = {
        0,-6,0,-4,-7,0,-5,0, 0,-6,0,-4,-7,0,-5,0
    };
    size_t done = 0;
    while (len - done >= 10) {
        const uint8x16_t input = load10(src + done);
        const uint8x16_t a = vqtbl1q_u8(input, vld1q_u8(enc_a));
        const uint8x16_t b = vqtbl1q_u8(input, vld1q_u8(enc_b));
        const uint8x16_t index = vandq_u8(vdupq_n_u8(31), vorrq_u8(
            vshlq_u8(a, vld1q_s8(shifts_a)), vshlq_u8(b, vld1q_s8(shifts_b))));
        const uint8x16_t high = vcgtq_u8(index, vdupq_n_u8(hex ? 9 : 25));
        const uint8x16_t offset = vbslq_u8(high,
            vdupq_n_u8(hex ? 55 : 24), vdupq_n_u8(hex ? 48 : 65));
        vst1q_u8(dst, vaddq_u8(index, offset));
        dst += 16;
        done += 10;
    }
    return done;
}

static size_t neon_decode(const uint8_t *src, size_t len, uint8_t *dst,
                         int hex, int checked)
{
    static const int8_t shifts_a[16] = {3,6,4,7,5,3,6,4,7,5,0,0,0,0,0,0};
    static const int8_t shifts_b[16] = {-2,1,-1,2,0,-2,1,-1,2,0,0,0,0,0,0,0};
    static const int8_t shifts_c[16] = {0,-4,0,-3,0,0,-4,0,-3,0,0,0,0,0,0,0};
    size_t done = 0;
    while (len - done >= 16) {
        const uint8x16_t input = vld1q_u8(src + done);
        const uint8x16_t upper = vandq_u8(input, vdupq_n_u8(0xdf));
        const uint8x16_t letter = vsubq_u8(upper, vdupq_n_u8('A'));
        const uint8x16_t digit = vsubq_u8(input, vdupq_n_u8(hex ? '0' : '2'));
        const uint8x16_t is_letter = vcleq_u8(letter, vdupq_n_u8(hex ? 21 : 25));
        const uint8x16_t is_digit = vcleq_u8(digit, vdupq_n_u8(hex ? 9 : 5));
        if (checked && vminvq_u8(vorrq_u8(is_letter, is_digit)) == 0) {
            break;
        }
        const uint8x16_t value = vbslq_u8(is_letter,
            vaddq_u8(letter, vdupq_n_u8(hex ? 10 : 0)),
            vaddq_u8(digit, vdupq_n_u8(hex ? 0 : 26)));
        const uint8x16_t a = vqtbl1q_u8(value, vld1q_u8(dec_a));
        const uint8x16_t b = vqtbl1q_u8(value, vld1q_u8(dec_b));
        const uint8x16_t c = vqtbl1q_u8(value, vld1q_u8(dec_c));
        store10(dst, vorrq_u8(vshlq_u8(a, vld1q_s8(shifts_a)),
            vorrq_u8(vshlq_u8(b, vld1q_s8(shifts_b)),
                     vshlq_u8(c, vld1q_s8(shifts_c)))));
        dst += 10;
        done += 16;
    }
    return done;
}
#elif defined(HS_BASE32_X86)

__attribute__((target("ssse3")))
static __m128i ssse3_mask(const uint8_t *bytes)
{
    const __m128i value = _mm_loadu_si128((const __m128i *)bytes);
    return value;
}

__attribute__((target("ssse3")))
static size_t ssse3_encode(const uint8_t *src, size_t len, uint8_t *dst, int hex)
{
    size_t done = 0;
    while (len - done >= 10) {
        uint8_t bytes[16] = {0};
        memcpy(bytes + 0, src + done + 0, 10);
        const __m128i value = _mm_loadu_si128((const __m128i *)bytes);
        __m128i output = _mm_setzero_si128();
        const __m128i a = _mm_shuffle_epi8(value, ssse3_mask(enc_a));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(a, 3),
            ssse3_mask((const uint8_t[16]){31,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(a, 2),
            ssse3_mask((const uint8_t[16]){0,0,0,0,0,31,0,0,0,0,0,0,0,31,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(a, 1),
            ssse3_mask((const uint8_t[16]){0,0,31,0,0,0,0,0,0,0,31,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            a,
            ssse3_mask((const uint8_t[16]){0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,31})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 1),
            ssse3_mask((const uint8_t[16]){0,0,0,0,30,0,0,0,0,0,0,0,30,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 2),
            ssse3_mask((const uint8_t[16]){0,28,0,0,0,0,0,0,0,28,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 3),
            ssse3_mask((const uint8_t[16]){0,0,0,0,0,0,24,0,0,0,0,0,0,0,24,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 4),
            ssse3_mask((const uint8_t[16]){0,0,0,16,0,0,0,0,0,0,0,16,0,0,0,0})));
        const __m128i b = _mm_shuffle_epi8(value, ssse3_mask(enc_b));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(b, 7),
            ssse3_mask((const uint8_t[16]){0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(b, 6),
            ssse3_mask((const uint8_t[16]){0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(b, 5),
            ssse3_mask((const uint8_t[16]){0,0,0,0,0,0,7,0,0,0,0,0,0,0,7,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(b, 4),
            ssse3_mask((const uint8_t[16]){0,0,0,15,0,0,0,0,0,0,0,15,0,0,0,0})));
        const __m128i high = _mm_cmpgt_epi8(output, _mm_set1_epi8(hex ? 9 : 25));
        const __m128i offset = _mm_or_si128(
            _mm_and_si128(high, _mm_set1_epi8(hex ? 55 : 24)),
            _mm_andnot_si128(high, _mm_set1_epi8(hex ? 48 : 65)));
        _mm_storeu_si128((__m128i *)dst, _mm_add_epi8(output, offset));
        dst += 16;
        done += 10;
    }
    return done;
}

__attribute__((target("ssse3")))
static size_t ssse3_decode(const uint8_t *src, size_t len, uint8_t *dst,
                          int hex, int checked)
{
    size_t done = 0;
    while (len - done >= 16) {
        const __m128i input = _mm_loadu_si128((const __m128i *)(src + done));
        const __m128i upper = _mm_and_si128(input, _mm_set1_epi8((char)0xdf));
        const __m128i letters = _mm_and_si128(
            _mm_cmpgt_epi8(upper, _mm_set1_epi8('A' - 1)),
            _mm_cmpgt_epi8(_mm_set1_epi8(hex ? 'V' + 1 : 'Z' + 1), upper));
        const __m128i digits = _mm_and_si128(
            _mm_cmpgt_epi8(input, _mm_set1_epi8(hex ? '0' - 1 : '2' - 1)),
            _mm_cmpgt_epi8(_mm_set1_epi8(hex ? '9' + 1 : '7' + 1), input));
        if (checked && (unsigned)_mm_movemask_epi8(_mm_or_si128(letters, digits))
            != 0xffffU) {
            break;
        }
        const __m128i value = _mm_or_si128(
            _mm_and_si128(letters,
                _mm_add_epi8(upper, _mm_set1_epi8(hex ? 10 - 'A' : -'A'))),
            _mm_and_si128(digits,
                _mm_add_epi8(input, _mm_set1_epi8(hex ? -'0' : 26 - '2'))));
        __m128i output = _mm_setzero_si128();
        const __m128i a = _mm_shuffle_epi8(value, ssse3_mask(dec_a));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 3),
            ssse3_mask((const uint8_t[16]){248,0,0,0,0,248,0,0,0,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 4),
            ssse3_mask((const uint8_t[16]){0,0,240,0,0,0,0,240,0,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 5),
            ssse3_mask((const uint8_t[16]){0,0,0,0,224,0,0,0,0,224,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 6),
            ssse3_mask((const uint8_t[16]){0,192,0,0,0,0,192,0,0,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(a, 7),
            ssse3_mask((const uint8_t[16]){0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0})));
        const __m128i b = _mm_shuffle_epi8(value, ssse3_mask(dec_b));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(b, 2),
            ssse3_mask((const uint8_t[16]){7,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(b, 1),
            ssse3_mask((const uint8_t[16]){0,0,15,0,0,0,0,15,0,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            b,
            ssse3_mask((const uint8_t[16]){0,0,0,0,31,0,0,0,0,31,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(b, 1),
            ssse3_mask((const uint8_t[16]){0,62,0,0,0,0,62,0,0,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_slli_epi16(b, 2),
            ssse3_mask((const uint8_t[16]){0,0,0,124,0,0,0,0,124,0,0,0,0,0,0,0})));
        const __m128i c = _mm_shuffle_epi8(value, ssse3_mask(dec_c));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(c, 4),
            ssse3_mask((const uint8_t[16]){0,15,0,0,0,0,15,0,0,0,0,0,0,0,0,0})));
        output = _mm_or_si128(output, _mm_and_si128(
            _mm_srli_epi16(c, 3),
            ssse3_mask((const uint8_t[16]){0,0,0,7,0,0,0,0,7,0,0,0,0,0,0,0})));
        uint8_t bytes[16];
        _mm_storeu_si128((__m128i *)bytes, output);
        memcpy(dst + 0, bytes + 0, 10);
        dst += 10;
        done += 16;
    }
    return done;
}

__attribute__((target("avx2")))
static __m256i avx2_mask(const uint8_t *bytes)
{
    const __m128i value = _mm_loadu_si128((const __m128i *)bytes);
    return _mm256_broadcastsi128_si256(value);
}

__attribute__((target("avx2")))
static size_t avx2_encode(const uint8_t *src, size_t len, uint8_t *dst, int hex)
{
    size_t done = 0;
    while (len - done >= 20) {
        uint8_t bytes[32] = {0};
        memcpy(bytes + 0, src + done + 0, 10);
        memcpy(bytes + 16, src + done + 10, 10);
        const __m256i value = _mm256_loadu_si256((const __m256i *)bytes);
        __m256i output = _mm256_setzero_si256();
        const __m256i a = _mm256_shuffle_epi8(value, avx2_mask(enc_a));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(a, 3),
            avx2_mask((const uint8_t[16]){31,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(a, 2),
            avx2_mask((const uint8_t[16]){0,0,0,0,0,31,0,0,0,0,0,0,0,31,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(a, 1),
            avx2_mask((const uint8_t[16]){0,0,31,0,0,0,0,0,0,0,31,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            a,
            avx2_mask((const uint8_t[16]){0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,31})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 1),
            avx2_mask((const uint8_t[16]){0,0,0,0,30,0,0,0,0,0,0,0,30,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 2),
            avx2_mask((const uint8_t[16]){0,28,0,0,0,0,0,0,0,28,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 3),
            avx2_mask((const uint8_t[16]){0,0,0,0,0,0,24,0,0,0,0,0,0,0,24,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 4),
            avx2_mask((const uint8_t[16]){0,0,0,16,0,0,0,0,0,0,0,16,0,0,0,0})));
        const __m256i b = _mm256_shuffle_epi8(value, avx2_mask(enc_b));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(b, 7),
            avx2_mask((const uint8_t[16]){0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(b, 6),
            avx2_mask((const uint8_t[16]){0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(b, 5),
            avx2_mask((const uint8_t[16]){0,0,0,0,0,0,7,0,0,0,0,0,0,0,7,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(b, 4),
            avx2_mask((const uint8_t[16]){0,0,0,15,0,0,0,0,0,0,0,15,0,0,0,0})));
        const __m256i high = _mm256_cmpgt_epi8(output, _mm256_set1_epi8(hex ? 9 : 25));
        const __m256i offset = _mm256_or_si256(
            _mm256_and_si256(high, _mm256_set1_epi8(hex ? 55 : 24)),
            _mm256_andnot_si256(high, _mm256_set1_epi8(hex ? 48 : 65)));
        _mm256_storeu_si256((__m256i *)dst, _mm256_add_epi8(output, offset));
        dst += 32;
        done += 20;
    }
    return done;
}

__attribute__((target("avx2")))
static size_t avx2_decode(const uint8_t *src, size_t len, uint8_t *dst,
                          int hex, int checked)
{
    size_t done = 0;
    while (len - done >= 32) {
        const __m256i input = _mm256_loadu_si256((const __m256i *)(src + done));
        const __m256i upper = _mm256_and_si256(input, _mm256_set1_epi8((char)0xdf));
        const __m256i letters = _mm256_and_si256(
            _mm256_cmpgt_epi8(upper, _mm256_set1_epi8('A' - 1)),
            _mm256_cmpgt_epi8(_mm256_set1_epi8(hex ? 'V' + 1 : 'Z' + 1), upper));
        const __m256i digits = _mm256_and_si256(
            _mm256_cmpgt_epi8(input, _mm256_set1_epi8(hex ? '0' - 1 : '2' - 1)),
            _mm256_cmpgt_epi8(_mm256_set1_epi8(hex ? '9' + 1 : '7' + 1), input));
        if (checked && (unsigned)_mm256_movemask_epi8(_mm256_or_si256(letters, digits))
            != 0xffffffffU) {
            break;
        }
        const __m256i value = _mm256_or_si256(
            _mm256_and_si256(letters,
                _mm256_add_epi8(upper, _mm256_set1_epi8(hex ? 10 - 'A' : -'A'))),
            _mm256_and_si256(digits,
                _mm256_add_epi8(input, _mm256_set1_epi8(hex ? -'0' : 26 - '2'))));
        __m256i output = _mm256_setzero_si256();
        const __m256i a = _mm256_shuffle_epi8(value, avx2_mask(dec_a));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 3),
            avx2_mask((const uint8_t[16]){248,0,0,0,0,248,0,0,0,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 4),
            avx2_mask((const uint8_t[16]){0,0,240,0,0,0,0,240,0,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 5),
            avx2_mask((const uint8_t[16]){0,0,0,0,224,0,0,0,0,224,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 6),
            avx2_mask((const uint8_t[16]){0,192,0,0,0,0,192,0,0,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(a, 7),
            avx2_mask((const uint8_t[16]){0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0})));
        const __m256i b = _mm256_shuffle_epi8(value, avx2_mask(dec_b));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(b, 2),
            avx2_mask((const uint8_t[16]){7,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(b, 1),
            avx2_mask((const uint8_t[16]){0,0,15,0,0,0,0,15,0,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            b,
            avx2_mask((const uint8_t[16]){0,0,0,0,31,0,0,0,0,31,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(b, 1),
            avx2_mask((const uint8_t[16]){0,62,0,0,0,0,62,0,0,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_slli_epi16(b, 2),
            avx2_mask((const uint8_t[16]){0,0,0,124,0,0,0,0,124,0,0,0,0,0,0,0})));
        const __m256i c = _mm256_shuffle_epi8(value, avx2_mask(dec_c));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(c, 4),
            avx2_mask((const uint8_t[16]){0,15,0,0,0,0,15,0,0,0,0,0,0,0,0,0})));
        output = _mm256_or_si256(output, _mm256_and_si256(
            _mm256_srli_epi16(c, 3),
            avx2_mask((const uint8_t[16]){0,0,0,7,0,0,0,0,7,0,0,0,0,0,0,0})));
        uint8_t bytes[32];
        _mm256_storeu_si256((__m256i *)bytes, output);
        memcpy(dst + 0, bytes + 0, 10);
        memcpy(dst + 10, bytes + 16, 10);
        dst += 20;
        done += 32;
    }
    return done;
}

#endif

int hs_base32_simd_backend(void)
{
#if defined(HS_BASE32_NEON)
    return 2;
#elif defined(HS_BASE32_X86)
#if !defined(HS_BASE32_SIMD_FORCE_SSSE3)
    /* Compiler CPU queries include OS support for AVX register state. */
    if (__builtin_cpu_supports("avx2")) {
        return 3;
    }
#endif
    return __builtin_cpu_supports("ssse3") ? 1 : 0;
#else
    return 0;
#endif
}

size_t hs_base32_simd_encode(const uint8_t *src, size_t len, uint8_t *dst, int hex)
{
#if defined(HS_BASE32_NEON)
    return neon_encode(src, len, dst, hex);
#elif defined(HS_BASE32_X86)
    const int backend = hs_base32_simd_backend();
    if (backend == 3) {
        return avx2_encode(src, len, dst, hex);
    }
    if (backend == 1) {
        return ssse3_encode(src, len, dst, hex);
    }
#endif
    (void)src;
    (void)len;
    (void)dst;
    (void)hex;
    return 0;
}

size_t hs_base32_simd_decode(const uint8_t *src, size_t len, uint8_t *dst, int hex)
{
#if defined(HS_BASE32_NEON)
    return neon_decode(src, len, dst, hex, 1);
#elif defined(HS_BASE32_X86)
    const int backend = hs_base32_simd_backend();
    if (backend == 3) {
        return avx2_decode(src, len, dst, hex, 1);
    }
    if (backend == 1) {
        return ssse3_decode(src, len, dst, hex, 1);
    }
#endif
    (void)src;
    (void)len;
    (void)dst;
    (void)hex;
    return 0;
}

/* Typed callers establish validity before entering these loops. */
size_t hs_base32_simd_decode_typed(const uint8_t *src, size_t len,
                                  uint8_t *dst, int hex)
{
#if defined(HS_BASE32_NEON)
    return neon_decode(src, len, dst, hex, 0);
#elif defined(HS_BASE32_X86)
    const int backend = hs_base32_simd_backend();
    if (backend == 3) {
        return avx2_decode(src, len, dst, hex, 0);
    }
    if (backend == 1) {
        return ssse3_decode(src, len, dst, hex, 0);
    }
#endif
    (void)src;
    (void)len;
    (void)dst;
    (void)hex;
    return 0;
}