packages feed

wireform-core-0.3.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.
 *
 * Widths (the same split as wireform-html and wireform-http1):
 * positions into a buffer are ptrdiff_t, so a ByteString or scan
 * range over 2 GiB is not truncated and a miss can be reported as
 * len (never -1 here); pure byte counts are size_t. Neither type
 * narrows to CInt on the Haskell side.
 */

#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <simde/x86/sse2.h>

/*
 * Scan buf[offset..len) for the first occurrence of target_byte.
 *
 * `offset` is the start position and `len` the buffer's length, which
 * doubles as the exclusive end of the scan, so both are indices into
 * `buf` and both must fit in ptrdiff_t (any real object does). Requires
 * 0 <= offset. Returns the absolute position of the match, or len if no
 * match was found (offset >= len is a miss that touches no memory).
 * Used by CSV delimiter scanning, NDJSON newline scanning, XML
 * structural scanning, etc.
 */
ptrdiff_t hs_find_byte(const uint8_t *buf, ptrdiff_t offset, ptrdiff_t len, uint8_t target_byte)
{
    ptrdiff_t 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. The bound is len - i (never
     * i + 16 <= len) so a len near PTRDIFF_MAX cannot wrap it. */
    for (; len - i >= 16; 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).
 *
 * `len` is a pure byte count (size_t): the kernel touches every
 * byte it is given, so it has no 2 GiB ceiling.
 *
 * 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, size_t 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;

    size_t 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 (len - i >= 16) {
        unsigned phase = (unsigned)(i & 3);
        uint8_t tile[16];
        for (int k = 0; k < 16; k++) {
            tile[k] = mb[(phase + (unsigned)k) & 3];
        }
        simde__m128i v = simde_mm_loadu_si128((const simde__m128i *)tile);
        /* len - i (not i + 16 <= len): no wrap for a len near SIZE_MAX. */
        for (; len - i >= 16; 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];
    }
}