Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions include/prism/compiler/accel.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
/**
* Platform detection for SIMD/fast-path implementations. At most one of these
* macros is defined, selecting the best available vectorization strategy.
*
* Selection is made at compile time with no runtime CPU detection, and
* precompiled binaries run on machines other than the one that built them. So
* each strategy relies only on instructions that the architecture baseline
* guarantees: NEON is mandatory on AArch64 and SSE2 is mandatory on x86-64.
*/
#if (defined(__aarch64__) && defined(__ARM_NEON)) || (defined(_MSC_VER) && defined(_M_ARM64))
# define PRISM_HAS_NEON
#elif (defined(__x86_64__) && defined(__SSSE3__)) || (defined(_MSC_VER) && defined(_M_X64))
# define PRISM_HAS_SSSE3
#elif (defined(__x86_64__) && defined(__SSE2__)) || (defined(_MSC_VER) && defined(_M_X64))
# define PRISM_HAS_SSE2
#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define PRISM_HAS_SWAR
#endif
Expand Down
2 changes: 1 addition & 1 deletion include/prism/internal/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ struct pm_parser_t {
*/
bool warn_mismatched_indentation;

#if defined(PRISM_HAS_NEON) || defined(PRISM_HAS_SSSE3) || defined(PRISM_HAS_SWAR)
#if defined(PRISM_HAS_NEON) || defined(PRISM_HAS_SSE2) || defined(PRISM_HAS_SWAR)
/*
* Cached lookup tables for pm_strpbrk's SIMD fast path. Avoids rebuilding
* the nibble-based tables on every call when the charset hasn't changed
Expand Down
6 changes: 3 additions & 3 deletions src/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -1891,7 +1891,7 @@ char_is_identifier_utf8(const uint8_t *b, ptrdiff_t n) {
* Up to three optimized implementations are selected at compile time, with a
* no-op fallback for unsupported platforms:
* 1. NEON — processes 16 bytes per iteration on aarch64.
* 2. SSSE3 — processes 16 bytes per iteration on x86-64.
* 2. SSE2 — processes 16 bytes per iteration on x86-64.
* 3. SWAR — little-endian fallback, processes 8 bytes per iteration.
*/

Expand Down Expand Up @@ -1949,8 +1949,8 @@ scan_identifier_ascii(const uint8_t *start, const uint8_t *end) {
return (size_t) (cursor - start);
}

#elif defined(PRISM_HAS_SSSE3)
#include <tmmintrin.h>
#elif defined(PRISM_HAS_SSE2)
#include <emmintrin.h>

static PRISM_INLINE size_t
scan_identifier_ascii(const uint8_t *start, const uint8_t *end) {
Expand Down
50 changes: 29 additions & 21 deletions src/strpbrk.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ pm_strpbrk_explicit_encoding_set(pm_parser_t *parser, uint32_t start, uint32_t l
* Up to three optimized implementations are selected at compile time, with a
* no-op fallback for unsupported platforms:
* 1. NEON — processes 16 bytes per iteration on aarch64.
* 2. SSSE3 — processes 16 bytes per iteration on x86-64.
* 2. SSE2 — processes 16 bytes per iteration on x86-64.
* 3. SWAR — little-endian fallback, processes 8 bytes per iteration.
*/

#if defined(PRISM_HAS_NEON) || defined(PRISM_HAS_SSSE3) || defined(PRISM_HAS_SWAR)
#if defined(PRISM_HAS_NEON) || defined(PRISM_HAS_SSE2) || defined(PRISM_HAS_SWAR)

/**
* Update the cached strpbrk lookup tables if the charset has changed. The
Expand All @@ -67,7 +67,7 @@ pm_strpbrk_explicit_encoding_set(pm_parser_t *parser, uint32_t start, uint32_t l
* skip table construction entirely.
*
* Builds three structures:
* - low_lut/high_lut: nibble-based lookup tables for SIMD matching (NEON/SSSE3)
* - low_lut/high_lut: nibble-based lookup tables for SIMD matching (NEON)
* - table: 256-bit bitmap for scalar fallback matching (all platforms)
*/
static PRISM_INLINE void
Expand Down Expand Up @@ -159,46 +159,54 @@ scan_strpbrk_ascii(pm_parser_t *parser, const uint8_t *source, size_t maximum, c
return false;
}

#elif defined(PRISM_HAS_SSSE3)
#include <tmmintrin.h>
#elif defined(PRISM_HAS_SSE2)
#include <emmintrin.h>

static PRISM_INLINE bool
scan_strpbrk_ascii(pm_parser_t *parser, const uint8_t *source, size_t maximum, const uint8_t *charset, size_t *index) {
pm_strpbrk_cache_update(parser, charset);

__m128i low_lut = _mm_loadu_si128((const __m128i *) parser->strpbrk_cache.low_lut);
__m128i high_lut = _mm_loadu_si128((const __m128i *) parser->strpbrk_cache.high_lut);
__m128i mask_0f = _mm_set1_epi8(0x0F);
/*
* SSE2 has no byte shuffle, so the nibble lookup used by NEON is not
* available. Instead, each charset byte is broadcast into its own vector
* and compared against the input directly. Charsets are short, so this is
* a handful of compares per 16 bytes.
*/
__m128i needles[sizeof(parser->strpbrk_cache.charset)];
size_t needles_size = 0;

for (size_t i = 0; i < sizeof(parser->strpbrk_cache.charset) && parser->strpbrk_cache.charset[i] != '\0'; i++) {
needles[needles_size++] = _mm_set1_epi8((char) parser->strpbrk_cache.charset[i]);
}

const __m128i zero = _mm_setzero_si128();
size_t idx = 0;

while (idx + 16 <= maximum) {
__m128i v = _mm_loadu_si128((const __m128i *) (source + idx));

// If any byte has the high bit set, stop.
/* If any byte has the high bit set, stop. */
if (_mm_movemask_epi8(v) != 0) break;

// Nibble-based classification using pshufb (SSSE3), same as NEON
// vqtbl1q_u8. A byte matches iff (low_lut[lo_nib] & high_lut[hi_nib]) != 0.
__m128i lo_class = _mm_shuffle_epi8(low_lut, _mm_and_si128(v, mask_0f));
__m128i hi_class = _mm_shuffle_epi8(high_lut, _mm_and_si128(_mm_srli_epi16(v, 4), mask_0f));
__m128i matched = _mm_and_si128(lo_class, hi_class);
/* NUL is always a breakpoint, matching pm_strpbrk_cache_update. */
__m128i matched = _mm_cmpeq_epi8(v, zero);
for (size_t i = 0; i < needles_size; i++) {
matched = _mm_or_si128(matched, _mm_cmpeq_epi8(v, needles[i]));
}

// Check if any byte matched.
int mask = _mm_movemask_epi8(_mm_cmpeq_epi8(matched, _mm_setzero_si128()));
int mask = _mm_movemask_epi8(matched);

if (mask == 0xFFFF) {
// All bytes were zero — no match in this chunk.
if (mask == 0) {
idx += 16;
continue;
}

// Find the first matching byte (first non-zero in matched).
*index = idx + pm_ctzll((uint64_t) (~mask & 0xFFFF));
/* Find the first matching byte. */
*index = idx + pm_ctzll((uint64_t) (mask & 0xFFFF));
return true;
}

// Scalar tail.
/* Scalar tail. */
while (idx < maximum && source[idx] < 0x80) {
uint8_t byte = source[idx];
if (parser->strpbrk_cache.table[byte >> 6] & ((uint64_t) 1 << (byte & 0x3F))) {
Expand Down
Loading