[gcc r17-3521] libcpp: Optimize AArch64 search_line_neon

Wilco Dijkstra via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:c5fe62b9f1ed3362c0b966c3f0e60f27b7d85572

commit r17-3521-gc5fe62b9f1ed3362c0b966c3f0e60f27b7d85572
Author: Wilco Dijkstra <[email protected]>
Date:   Wed Aug 12 15:07:49 2026 +0000

    libcpp: Optimize AArch64 search_line_neon
    
    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.
    
    libcpp/ChangeLog:
    
            * lex.cc (search_line_neon): New optimized AdvSIMD implementation.

Diff:
---
 libcpp/lex.cc | 125 ++++++++++++++++++++--------------------------------------
 1 file changed, 42 insertions(+), 83 deletions(-)

diff --git a/libcpp/lex.cc b/libcpp/lex.cc
index 047765fe733f..0e4d29a2950a 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, 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.