base16-2.0: test/simd-native.c
/* Copyright (c) 2026, Emily Pillmore. BSD-3-Clause; see LICENSE. */
/*
* Standalone differential/bounds checks; link with cbits/base16_simd.c.
* Recommended: -std=c99 -O2 -Wall -Wextra -Werror
* -fsanitize=address,undefined -fno-omit-frame-pointer
* Repeat with HS_BASE16_SIMD_FORCE_SCALAR and HS_BASE16_SIMD_FORCE_SSSE3.
*/
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE 1
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__unix__) || defined(__APPLE__)
#include <sys/mman.h>
#include <unistd.h>
#define HS_BASE16_GUARD_PAGES 1
#endif
#include "base16_simd.h"
#define CHECK(condition) do { \
if (!(condition)) { \
fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #condition); \
exit(EXIT_FAILURE); \
} \
} while (0)
static const uint8_t alphabet[] = "0123456789abcdef";
static size_t encode_block;
static size_t decode_block;
static size_t checks;
/* Deliberately scalar and independent of the kernels' vector classification. */
static int hex_value(uint8_t value)
{
if (value >= '0' && value <= '9') {
return value - '0';
}
if (value >= 'a' && value <= 'f') {
return value - 'a' + 10;
}
if (value >= 'A' && value <= 'F') {
return value - 'A' + 10;
}
return -1;
}
static size_t encode_prefix(size_t len)
{
return encode_block == 0 ? 0 : len / encode_block * encode_block;
}
static size_t decode_prefix(const uint8_t *src, size_t len)
{
size_t consumed = 0;
if (decode_block == 0) {
return 0;
}
while (len - consumed >= decode_block) {
for (size_t i = 0; i < decode_block; i++) {
if (hex_value(src[consumed + i]) < 0) {
return consumed;
}
}
consumed += decode_block;
}
return consumed;
}
static void scalar_encode(const uint8_t *src, size_t len, uint8_t *dst)
{
for (size_t i = 0; i < len; i++) {
*dst++ = alphabet[src[i] / 16];
*dst++ = alphabet[src[i] % 16];
}
}
static void scalar_decode(const uint8_t *src, size_t len, uint8_t *dst)
{
for (size_t i = 0; i < len; i += 2) {
*dst++ = (uint8_t)(hex_value(src[i]) * 16 + hex_value(src[i + 1]));
}
}
static void check_canary(const uint8_t *data, size_t len)
{
for (size_t i = 0; i < len; i++) {
CHECK(data[i] == 0xa5);
}
}
static void check_encode(const uint8_t *src, size_t len, size_t offset)
{
const size_t expected = encode_prefix(len);
const size_t output_len = expected * 2;
uint8_t *output = malloc(offset + output_len + 32);
uint8_t *reference = malloc(output_len + 1);
size_t consumed;
CHECK(output != NULL && reference != NULL);
memset(output, 0xa5, offset + output_len + 32);
scalar_encode(src, expected, reference);
consumed = hs_base16_simd_encode(src, len, output + offset);
CHECK(consumed == expected && consumed <= len);
CHECK(memcmp(output + offset, reference, output_len) == 0);
check_canary(output, offset);
check_canary(output + offset + output_len, 32);
free(reference);
free(output);
checks++;
}
static void check_decode(const uint8_t *src, size_t len, size_t offset)
{
const size_t expected = decode_prefix(src, len);
const size_t output_len = expected / 2;
uint8_t *output = malloc(offset + output_len + 32);
uint8_t *reference = malloc(output_len + 1);
size_t consumed;
CHECK(output != NULL && reference != NULL);
memset(output, 0xa5, offset + output_len + 32);
scalar_decode(src, expected, reference);
consumed = hs_base16_simd_decode(src, len, output + offset);
CHECK(consumed == expected);
CHECK(consumed % 2 == 0 && consumed <= len);
CHECK(memcmp(output + offset, reference, output_len) == 0);
check_canary(output, offset);
check_canary(output + offset + output_len, 32);
free(reference);
free(output);
checks++;
}
static void test_lengths_and_alignment(void)
{
uint8_t source[1056];
uint8_t encoded[2112];
for (size_t offset = 0; offset < 32; offset++) {
for (size_t i = 0; i < sizeof(source); i++) {
source[i] = (uint8_t)(i * 73 + offset * 19);
}
scalar_encode(source, sizeof(source), encoded);
for (size_t len = 0; len <= 1024; len++) {
check_encode(source + offset, len, 31 - offset);
check_decode(encoded + offset, len, 31 - offset);
}
}
/* Vary both alignments independently at every relevant vector boundary. */
const size_t sizes[] = {0, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65,
127, 128, 129, 255, 256, 257};
for (size_t source_offset = 0; source_offset < 32; source_offset++) {
for (size_t output_offset = 0; output_offset < 32; output_offset++) {
for (size_t i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
check_encode(source + source_offset, sizes[i], output_offset);
check_decode(encoded + source_offset, sizes[i], output_offset);
}
}
}
}
static void test_all_byte_values(void)
{
uint8_t source[257];
uint8_t encoded[514];
for (unsigned value = 0; value <= 255; value++) {
memset(source, (int)value, sizeof(source));
check_encode(source, sizeof(source), value % 32);
scalar_encode(source, sizeof(source), encoded);
check_decode(encoded, sizeof(encoded), value % 32);
for (size_t i = 0; i < sizeof(encoded); i++) {
if (encoded[i] >= 'a' && encoded[i] <= 'f' && i % 2 == 0) {
encoded[i] = (uint8_t)(encoded[i] - 'a' + 'A');
}
}
check_decode(encoded, sizeof(encoded), value % 32);
}
}
static void test_invalid_block_stops(void)
{
uint8_t source[580];
for (size_t position = 0; position < sizeof(source); position++) {
for (unsigned value = 0; value <= 255; value++) {
memset(source, 'a', sizeof(source));
source[position] = (uint8_t)value;
check_decode(source, sizeof(source), position % 32);
}
}
}
static uint32_t next_random(uint32_t *state)
{
uint32_t value = *state;
value ^= value << 13;
value ^= value >> 17;
value ^= value << 5;
*state = value;
return value;
}
static void test_random_mixed_inputs(void)
{
uint8_t source[4096];
uint8_t encoded[8192];
uint32_t state = UINT32_C(0x6d253fb9);
for (size_t sample = 0; sample < 256; sample++) {
const size_t len = next_random(&state) % (sizeof(source) + 1);
const size_t encoded_len = len * 2;
const size_t offset = next_random(&state) % 32;
for (size_t i = 0; i < len; i++) {
source[i] = (uint8_t)next_random(&state);
}
check_encode(source, len, offset);
scalar_encode(source, len, encoded);
for (size_t i = 0; i < encoded_len; i++) {
if (encoded[i] >= 'a' && encoded[i] <= 'f' && next_random(&state) % 2) {
encoded[i] = (uint8_t)(encoded[i] - 'a' + 'A');
}
}
check_decode(encoded, encoded_len, offset);
if (encoded_len > 0) {
const size_t position = next_random(&state) % encoded_len;
encoded[position] = (uint8_t)next_random(&state);
check_decode(encoded, encoded_len, offset);
}
}
}
static void test_tight_allocations(void)
{
for (size_t len = 1; len <= 1024; len++) {
uint8_t *source = malloc(len);
const size_t encode_len = encode_prefix(len) * 2;
uint8_t *encoded = malloc(encode_len == 0 ? 1 : encode_len);
size_t decode_len;
uint8_t *decoded;
CHECK(source != NULL && encoded != NULL);
memset(source, 0xfb, len);
CHECK(hs_base16_simd_encode(source, len, encoded) == encode_prefix(len));
memset(source, 'F', len);
decode_len = decode_prefix(source, len) / 2;
decoded = malloc(decode_len == 0 ? 1 : decode_len);
CHECK(decoded != NULL);
CHECK(hs_base16_simd_decode(source, len, decoded) == decode_prefix(source, len));
free(decoded);
free(encoded);
free(source);
checks += 2;
}
}
#if defined(HS_BASE16_GUARD_PAGES)
static uint8_t *guarded_buffer(size_t page_size)
{
uint8_t *mapping = mmap(NULL, page_size * 3, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
CHECK(mapping != MAP_FAILED);
CHECK(mprotect(mapping + page_size, page_size, PROT_READ | PROT_WRITE) == 0);
return mapping;
}
static void test_guard_pages(void)
{
const long raw_page_size = sysconf(_SC_PAGESIZE);
size_t page_size;
uint8_t *source_mapping;
uint8_t *output_mapping;
CHECK(raw_page_size >= 2048);
page_size = (size_t)raw_page_size;
source_mapping = guarded_buffer(page_size);
output_mapping = guarded_buffer(page_size);
for (size_t len = 0; len <= 1024; len++) {
for (size_t at_end = 0; at_end < 2; at_end++) {
uint8_t *source = source_mapping + page_size +
(at_end ? page_size - len : 0);
const size_t encode_len = encode_prefix(len) * 2;
uint8_t *output = output_mapping + page_size +
(at_end ? page_size - encode_len : 0);
size_t expected;
size_t decode_len;
memset(source, 0xfb, len);
CHECK(hs_base16_simd_encode(source, len, output) == encode_prefix(len));
memset(source, 'F', len);
expected = decode_prefix(source, len);
decode_len = expected / 2;
output = output_mapping + page_size + (at_end ? page_size - decode_len : 0);
CHECK(hs_base16_simd_decode(source, len, output) == expected);
checks += 2;
}
}
CHECK(munmap(source_mapping, page_size * 3) == 0);
CHECK(munmap(output_mapping, page_size * 3) == 0);
}
#endif
int main(int argc, char **argv)
{
const int backend = hs_base16_simd_backend();
if (argc != 1) {
if (argc != 3 || strcmp(argv[1], "--require-backend") != 0 ||
strlen(argv[2]) != 1 || argv[2][0] < '0' || argv[2][0] > '3') {
fprintf(stderr, "usage: %s [--require-backend 0|1|2|3]\n", argv[0]);
return EXIT_FAILURE;
}
if (backend != argv[2][0] - '0') {
fprintf(stderr, "required SIMD backend %c, detected %d\n", argv[2][0], backend);
return EXIT_FAILURE;
}
}
CHECK(backend >= 0 && backend <= 3);
#if defined(HS_BASE16_SIMD_FORCE_SCALAR)
CHECK(backend == 0);
#elif defined(HS_BASE16_SIMD_FORCE_SSSE3) && defined(__x86_64__)
CHECK(backend == 0 || backend == 1);
#elif defined(__aarch64__) && defined(__ARM_NEON) && \
(defined(__GNUC__) || defined(__clang__))
CHECK(backend == 2);
#endif
encode_block = backend == 3 ? 32 : backend == 0 ? 0 : 16;
decode_block = backend == 1 ? 16 : backend == 0 ? 0 : 32;
printf("SIMD backend: %d (%s)\n", backend,
backend == 3 ? "AVX2" : backend == 2 ? "NEON" :
backend == 1 ? "SSSE3" : "scalar fallback");
/* Zero-length and unsupported calls must not dereference their pointers. */
CHECK(hs_base16_simd_encode(NULL, 0, NULL) == 0);
CHECK(hs_base16_simd_decode(NULL, 0, NULL) == 0);
if (backend == 0) {
CHECK(hs_base16_simd_encode(NULL, SIZE_MAX, NULL) == 0);
CHECK(hs_base16_simd_decode(NULL, SIZE_MAX, NULL) == 0);
}
test_lengths_and_alignment();
test_all_byte_values();
test_invalid_block_stops();
test_random_mixed_inputs();
test_tight_allocations();
#if defined(HS_BASE16_GUARD_PAGES)
test_guard_pages();
#endif
printf("Passed %zu differential/bounds checks.\n", checks);
return EXIT_SUCCESS;
}