wireform-core-0.2.0.0: cbits/fast_scan.c
/*
* Format-agnostic SIMD byte-scanning primitives.
*
* These used to live in wireform-xml/cbits/fast_xml.c under
* XML-specific names because that's where they were first
* introduced. CSV, NDJSON, and a few other decoders reached
* into that file to reuse hs_xml_find_byte; to keep the
* dependency graph honest the generic helper lives here and is
* exported under a neutral name. The XML-specific scanners
* (find_lt, find_attr_end, find_cdata_end, etc.) remain in
* wireform-xml since they encode XML-grammar knowledge.
*
* Uses SSE2 via simde for portable SIMD. Fallback to scalar
* loops on architectures without simde support.
*/
#include <stdint.h>
#include <string.h>
#include <simde/x86/sse2.h>
/*
* Scan buf[offset..len) for the first occurrence of target_byte.
* Returns the absolute position of the match, or len if no
* match was found. Used by CSV delimiter scanning, NDJSON
* newline scanning, XML structural scanning, etc.
*/
int hs_find_byte(const uint8_t *buf, int offset, int len, uint8_t target_byte)
{
int i = offset;
simde__m128i target = simde_mm_set1_epi8((char)target_byte);
/* Scalar prologue until 16-byte aligned. */
while (i < len && ((uintptr_t)(buf + i) & 15) != 0) {
if (buf[i] == target_byte) return i;
i++;
}
/* SIMD main loop: 16 bytes at a time. */
for (; i + 16 <= len; i += 16) {
simde__m128i chunk = simde_mm_load_si128((const simde__m128i *)(buf + i));
int mask = simde_mm_movemask_epi8(simde_mm_cmpeq_epi8(chunk, target));
if (mask != 0) return i + __builtin_ctz(mask);
}
/* Scalar epilogue for the remaining <16 bytes. */
for (; i < len; i++) {
if (buf[i] == target_byte) return i;
}
return len;
}
/*
* In-place XOR of a 4-byte repeating mask over @buf[0..len).
* Used by wireform-websocket for RFC 6455 sec 5.3 frame masking
* (both directions: the parser un-masks an inbound payload; the
* builder masks an outbound client payload).
*
* The mask repeats in 4-byte network-order chunks; for the SSE2
* inner loop we broadcast that 32-bit pattern into a 16-byte
* vector once and XOR 16 bytes per iteration. Scalar prologue +
* epilogue handle any sub-16-byte head / tail.
*
* @mask is a uint32_t with the four mask bytes in NETWORK ORDER,
* i.e. byte 0 of the mask in the high byte. The same byte
* ordering the wire format uses.
*/
void hs_ws_mask(uint8_t *buf, int len, uint32_t mask)
{
/* Build the byte ordering the loop needs: mask[0] at offset 0,
* mask[1] at offset 1, … Easiest done by re-laying the bytes. */
uint8_t mb[4];
mb[0] = (uint8_t)(mask >> 24);
mb[1] = (uint8_t)(mask >> 16);
mb[2] = (uint8_t)(mask >> 8);
mb[3] = (uint8_t) mask;
int i = 0;
/* Align the scalar prologue so the main loop can use 'movdqa'
* once we hit a 16-byte boundary. The mask byte at offset i
* is mb[i & 3] because the mask repeats every 4 bytes. */
while (i < len && ((uintptr_t)(buf + i) & 15) != 0) {
buf[i] ^= mb[i & 3];
i++;
}
/* SSE2 main loop: 16 bytes per iteration. The 4-byte mask
* tiled into a 16-byte vector once. If i hit alignment at an
* offset where i & 3 != 0, the tile rotates accordingly. */
if (i + 16 <= len) {
int phase = i & 3;
uint8_t tile[16];
for (int k = 0; k < 16; k++) {
tile[k] = mb[(phase + k) & 3];
}
simde__m128i v = simde_mm_loadu_si128((const simde__m128i *)tile);
for (; i + 16 <= len; i += 16) {
simde__m128i chunk = simde_mm_load_si128((const simde__m128i *)(buf + i));
chunk = simde_mm_xor_si128(chunk, v);
simde_mm_store_si128((simde__m128i *)(buf + i), chunk);
}
}
/* Scalar epilogue for the trailing <16 bytes. */
for (; i < len; i++) {
buf[i] ^= mb[i & 3];
}
}