> On 18 Aug 2026, at 22:33, Wilco Dijkstra <[email protected]> wrote:
> 
> v2: Based on Alexander's feedback, make use of the padding to get further
> speedup. Improve the algorithm to use single-reg TBL lookup.
> 
> The existing AdvSIMD search_line_neon is ancient and not well optimized.
> Optimize it for typical inputs (short lines) - what matters is to quickly
> get the match result for the first few vectors with minimal initialization
> overhead.  We simply loop until a match is found even if the input pointer
> is unaligned or close to the end.  Since this may overread, it relies on
> *end containing a match and CPP_BUFFER_PADDING >= 16.
> 
> Lookup the low 4 bits of each character using TBL and compare the result
> with the original input.  The lookup table contains the 4 search characters
> at entries MOD 16, so a match results in the same character.  This works
> because the low 4 bits of the search characters are unique.
> 
> The optimized sequence is less than half that of the old code:
> 
> .L44:
>        ldr     q30, [x0, 16]!
> .L43:
>        and     v31.16b, v30.16b, v28.16b
>        tbl     v31.16b, {v29.16b}, v31.16b
>        cmeq    v31.16b, v31.16b, v30.16b
>        addhn   v31.8b, v31.8h, v31.8h
>        fmov    x1, d31
>        cbz     x1, .L44
>        and     x1, x1, 1229782938247303441
>        rbit    x1, x1
>        clz     x1, x1
>        add     x0, x0, x1, lsr 2
>        ret
> 
> Performance is significantly better as a result: on a huge trace that replays
> the calls from libcpp it is ~54% faster on Neoverse V2 and ~42% on Neoverse 
> N1.
> 
> Passes bootstrap, OK for commit?

Ok.
Thanks,
Kyrill

> 
> libcpp/ChangeLog:
> 
>        * lex.cc (search_line_neon): New optimized AdvSIMD implementation.
> 
> ---
> 
> diff --git a/libcpp/lex.cc b/libcpp/lex.cc
> index 
> ee6d6e01803d8a98b2136e51ed92136af30f32fd..84318db97ec8168a9022a4493a050e3f09fbb61b
>  100644
> --- a/libcpp/lex.cc
> +++ b/libcpp/lex.cc
> @@ -644,96 +644,55 @@ search_line_fast (const uchar *s, const uchar *end 
> ATTRIBUTE_UNUSED)
> #elif defined (__ARM_NEON) && defined (__ARM_64BIT_STATE)
> #include "arm_neon.h"
> 
> -/* This doesn't have to be the exact page size, but no system may use
> -   a size smaller than this.  ARMv8 requires a minimum page size of
> -   4k.  The impact of being conservative here is a small number of
> -   cases will take the slightly slower entry path into the main
> -   loop.  */
> -
> -#define AARCH64_MIN_PAGE_SIZE 4096
> +/* A fast AdvSIMD scanner implementation using table lookup.
> +
> +   Lookup the low 4 bits of each character using TBL and compare the
> +   result with the original input.  The lookup table contains the 4
> +   search characters at entries MOD 16, so a match results in the same
> +   character.  This works because the low 4 bits of the search
> +   characters are unique.
> +
> +   Typical statistics for number of characters till a match:
> +    1-15: 30.9%
> +   16-31: 22.2%
> +   32-47: 18.3%
> +   48-63: 14.1%
> +   64-79: 13.0%
> +   80-95:  1.1%
> +     >96:  0.3%
> +
> +   To get good performance, what matters is to quickly get the match result
> +   for the first few vectors with minimal initialization overhead.
> +   We simply loop until a match is found even if the input pointer is
> +   unaligned or close to the end.  Since this may overread, it relies on
> +   *end containing a match and CPP_BUFFER_PADDING >= 16.  */
> +
> +static_assert (CPP_BUFFER_PADDING >= 16, "");
> +
> +/* TBL lookup with each search char at (ch % 16).  Avoid matching NUL by
> +   setting the first entry to 1.  */
> +static const uint8_t table[16] =
> +  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\n', 0, '\\', '\r', 0, '?' };
> 
> static const uchar *
> search_line_neon (const uchar *s, const uchar *end ATTRIBUTE_UNUSED)
> {
> -  const uint8x16_t repl_nl = vdupq_n_u8 ('\n');
> -  const uint8x16_t repl_cr = vdupq_n_u8 ('\r');
> -  const uint8x16_t repl_bs = vdupq_n_u8 ('\\');
> -  const uint8x16_t repl_qm = vdupq_n_u8 ('?');
> -  const uint8x16_t xmask = (uint8x16_t) vdupq_n_u64 (0x8040201008040201ULL);
> +  uint8x16_t d, res, tab, m;
> +  uint16x8_t t;
> +  uint64_t mask;
> +  m = vdupq_n_u8 (0xf);
> +  tab = vld1q_u8 (table);
> +  d = vld1q_u8 (s);
> 
> -#ifdef __ARM_BIG_ENDIAN
> -  const int16x8_t shift = {8, 8, 8, 8, 0, 0, 0, 0};
> -#else
> -  const int16x8_t shift = {0, 0, 0, 0, 8, 8, 8, 8};
> -#endif
> -
> -  unsigned int found;
> -  const uint8_t *p;
> -  uint8x16_t data;
> -  uint8x16_t t;
> -  uint16x8_t m;
> -  uint8x16_t u, v, w;
> -
> -  /* Align the source pointer.  */
> -  p = (const uint8_t *)((uintptr_t)s & -16);
> -
> -  /* Assuming random string start positions, with a 4k page size we'll take
> -     the slow path about 0.37% of the time.  */
> -  if (__builtin_expect ((AARCH64_MIN_PAGE_SIZE
> - - (((uintptr_t) s) & (AARCH64_MIN_PAGE_SIZE - 1)))
> - < 16, 0))
> -    {
> -      /* Slow path: the string starts near a possible page boundary.  */
> -      uint32_t misalign, mask;
> -
> -      misalign = (uintptr_t)s & 15;
> -      mask = (-1u << misalign) & 0xffff;
> -      data = vld1q_u8 (p);
> -      t = vceqq_u8 (data, repl_nl);
> -      u = vceqq_u8 (data, repl_cr);
> -      v = vorrq_u8 (t, vceqq_u8 (data, repl_bs));
> -      w = vorrq_u8 (u, vceqq_u8 (data, repl_qm));
> -      t = vorrq_u8 (v, w);
> -      t = vandq_u8 (t, xmask);
> -      m = vpaddlq_u8 (t);
> -      m = vshlq_u16 (m, shift);
> -      found = vaddvq_u16 (m);
> -      found &= mask;
> -      if (found)
> - return (const uchar*)p + __builtin_ctz (found);
> -    }
> -  else
> +  while (1)
>     {
> -      data = vld1q_u8 ((const uint8_t *) s);
> -      t = vceqq_u8 (data, repl_nl);
> -      u = vceqq_u8 (data, repl_cr);
> -      v = vorrq_u8 (t, vceqq_u8 (data, repl_bs));
> -      w = vorrq_u8 (u, vceqq_u8 (data, repl_qm));
> -      t = vorrq_u8 (v, w);
> -      if (__builtin_expect (vpaddd_u64 ((uint64x2_t)t) != 0, 0))
> - goto done;
> +      t = (uint16x8_t) vceqq_u8 (vqtbl1q_u8 (tab, vandq_u8 (d, m)), d);
> +      mask = vget_lane_u64 ((uint64x1_t)vaddhn_u16 (t, t), 0);
> +      if (mask != 0)
> + return s + (__builtin_ctzl (mask & 0x1111111111111111UL) >> 2);
> +      s += 16;
> +      d = vld1q_u8 (s);
>     }
> -
> -  do
> -    {
> -      p += 16;
> -      data = vld1q_u8 (p);
> -      t = vceqq_u8 (data, repl_nl);
> -      u = vceqq_u8 (data, repl_cr);
> -      v = vorrq_u8 (t, vceqq_u8 (data, repl_bs));
> -      w = vorrq_u8 (u, vceqq_u8 (data, repl_qm));
> -      t = vorrq_u8 (v, w);
> -    } while (!vpaddd_u64 ((uint64x2_t)t));
> -
> -done:
> -  /* Now that we've found the terminating substring, work out precisely where
> -     we need to stop.  */
> -  t = vandq_u8 (t, xmask);
> -  m = vpaddlq_u8 (t);
> -  m = vshlq_u16 (m, shift);
> -  found = vaddvq_u16 (m);
> -  return (((((uintptr_t) p) < (uintptr_t) s) ? s : (const uchar *)p)
> -  + __builtin_ctz (found));
> }
> 
> #ifdef HAVE_SVE2
> 

Reply via email to