From bd4df8f2f7b4594ab4a00c857c9ae5ffe255512d Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
Date: Tue, 11 Aug 2026 08:45:27 +0000
Subject: [PATCH 1/3] Add AltiVec/VMX SIMD primitives and build detection

Adds the primitive layer and configure support for PowerPC AltiVec (VMX),
targeting cores that have AltiVec but no VSX: e6500 (QorIQ T-series), e600
/MPC74xx (G4), PPC970 (G5) and POWER6.

Two properties of VMX shape the design:

- VMX has no unaligned vector load.  GCC does not synthesize one on non-VSX
  targets either: it spills through the stack (4x lwz + 4x stw + lvx), which
  is slower than scalar code.  simd_ldu() therefore implements the classic
  lvsl + vec_perm idiom by hand.  The load control is a separate value from
  simd_ldu_init() so callers can hoist it out of the loop: the search loops
  advance by 16 bytes, so the alignment of a stream never changes.  vec_ld(15,p)
  never touches a page that p[15] does not, so there is no over-read.
  When VSX is available the same call sites compile to a single vec_xl.

- VMX has no equivalent of _mm_movemask_epi8().  Whole vector tests instead
  use the record form compares, which set CR6 and branch with no vector to GPR
  round trip, so simd_mask_any() is cheaper than the SSE2 movemask idiom.
  Extracting individual match positions does need a store, so simd_mask_t is
  provided for the (unlikely) hit path.

altivec.h defines vector, pixel and bool as macros, which breaks <vector> and
bool in C++, so they are undefined right after the include and the __vector
spelling is used throughout.

configure gates on an actual compile/run test rather than host_cpu alone,
because e500v1/v2 are powerpc-*-* but have SPE and not AltiVec.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSbbKgkiRQmVj792gErwVD
---
 configure.ac          | 69 +++++++++++++++++++++++++++++++++++++++++
 include/reflex/simd.h | 72 +++++++++++++++++++++++++++++++++++++++++++
 src/ugrep.cpp         |  2 ++
 3 files changed, 143 insertions(+)

diff --git a/configure.ac b/configure.ac
index 2f46be3..18e6c8f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -96,6 +96,13 @@ AC_ARG_ENABLE(neon,
   [with_no_neon="yes"],
   [with_no_neon="no"])
 
+# --disable-altivec fallback option if cross compilation fails to disable AltiVec/VMX CPU extensions
+AC_ARG_ENABLE(altivec,
+  [AS_HELP_STRING([--disable-altivec],
+                 [disable SIMD AltiVec/VMX CPU extensions])],
+  [with_no_altivec="yes"],
+  [with_no_altivec="no"])
+
 # SIMD_FLAGS applies to all source code
 SIMD_FLAGS=
 # SIMD_AVX2_FLAGS only applies to lib/matcher_avx2.cpp which is selected based on a runtime AVX2 check
@@ -198,6 +205,37 @@ if test "x$cross_compiling" = "xyes"; then
         SIMD_FLAGS="-DHAVE_NEON"
       fi
     ;;
+    powerpc*|ppc*)
+      # enable AltiVec/VMX extensions, note that e500v1/v2 have SPE and not AltiVec, hence the compile test
+      if ! test "x$with_no_altivec" = "xyes"; then
+        AC_MSG_CHECKING([whether ${CXX} supports AltiVec/VMX intrinsics])
+        save_CXXFLAGS=$CXXFLAGS
+        CXXFLAGS="-maltivec -mabi=altivec"
+        AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <altivec.h>
+#undef vector
+#undef pixel
+#undef bool]], [[__vector unsigned char n = vec_splats((unsigned char)42); (void)vec_any_ne(n, n);]])],
+                          [maltivec_ok=yes],
+                          [maltivec_ok=no])
+        if test "x$maltivec_ok" = "xyes"; then
+          SIMD_FLAGS="-maltivec -mabi=altivec -DHAVE_ALTIVEC"
+        else
+          # -mabi=altivec is a 32 bit SysV option that some targets reject, retry without it
+          CXXFLAGS="-maltivec"
+          AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <altivec.h>
+#undef vector
+#undef pixel
+#undef bool]], [[__vector unsigned char n = vec_splats((unsigned char)42); (void)vec_any_ne(n, n);]])],
+                            [maltivec_ok=yes],
+                            [maltivec_ok=no])
+          if test "x$maltivec_ok" = "xyes"; then
+            SIMD_FLAGS="-maltivec -DHAVE_ALTIVEC"
+          fi
+        fi
+        CXXFLAGS=$save_CXXFLAGS
+        AC_MSG_RESULT($maltivec_ok)
+      fi
+    ;;
   esac
 
 else
@@ -286,6 +324,37 @@ else
     fi
   fi
 
+  if test -z "$SIMD_FLAGS"; then
+    if ! test "x$with_no_altivec" = "xyes"; then
+      AC_MSG_CHECKING([whether ${CXX} supports AltiVec/VMX intrinsics])
+      save_CXXFLAGS=$CXXFLAGS
+      CXXFLAGS="-maltivec -mabi=altivec"
+      AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <altivec.h>
+#undef vector
+#undef pixel
+#undef bool]], [[__vector unsigned char n = vec_splats((unsigned char)42); (void)vec_any_ne(n, n);]])],
+                    [maltivec_ok=yes],
+                    [maltivec_ok=no])
+      if test "x$maltivec_ok" = "xyes"; then
+        SIMD_FLAGS="-maltivec -mabi=altivec -DHAVE_ALTIVEC"
+      else
+        # -mabi=altivec is a 32 bit SysV option that some targets reject, retry without it
+        CXXFLAGS="-maltivec"
+        AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <altivec.h>
+#undef vector
+#undef pixel
+#undef bool]], [[__vector unsigned char n = vec_splats((unsigned char)42); (void)vec_any_ne(n, n);]])],
+                      [maltivec_ok=yes],
+                      [maltivec_ok=no])
+        if test "x$maltivec_ok" = "xyes"; then
+          SIMD_FLAGS="-maltivec -DHAVE_ALTIVEC"
+        fi
+      fi
+      CXXFLAGS=$save_CXXFLAGS
+      AC_MSG_RESULT($maltivec_ok)
+    fi
+  fi
+
 fi
 
 AC_SUBST(SIMD_FLAGS)
diff --git a/include/reflex/simd.h b/include/reflex/simd.h
index ccaee55..2831d4e 100644
--- a/include/reflex/simd.h
+++ b/include/reflex/simd.h
@@ -52,6 +52,13 @@
 # if defined(__ARM_ACLE)
 #  include <arm_acle.h>
 # endif
+#elif defined(HAVE_ALTIVEC)
+# include <altivec.h>
+// altivec.h defines vector, pixel and bool as macros, which breaks C++ <vector> and bool:
+// undefine them and use the __vector, __pixel and __bool spellings instead
+# undef vector
+# undef pixel
+# undef bool
 #endif
 
 #if defined(HAVE_AVX512BW) || defined(HAVE_AVX2) || defined(HAVE_SSE2)
@@ -165,6 +172,71 @@ inline uint32_t ctzl(uint64_t x)
 }
 #endif
 
+#elif defined(HAVE_ALTIVEC)
+
+namespace reflex {
+
+/// Splat a byte over a 16 byte AltiVec vector
+inline __vector unsigned char simd_set1_u8(uint8_t c)
+{
+  return vec_splats(c);
+}
+
+/// Overlay to read a byte compare mask (0x00 or 0xff per byte) as four 32 bit words, AltiVec has no
+/// equivalent of SSE2 _mm_movemask_epi8() and NEON has none either, so store it and scan the bytes
+union simd_mask_t {
+  __vector unsigned char v;
+  uint32_t                u[4];
+};
+
+/// True when a byte compare mask has at least one match, a record form compare and a branch on CR6
+inline bool simd_mask_any(__vector unsigned char vmask)
+{
+  return vec_any_ne(vmask, vec_splats(static_cast<unsigned char>(0)));
+}
+
+#if defined(__VSX__)
+
+/// Load control for simd_ldu(), unused when VSX provides an unaligned vector load
+typedef int simd_ldu_t;
+
+/// Obtain the load control to load 16 unaligned bytes at s and at any address congruent to s modulo 16
+inline simd_ldu_t simd_ldu_init(const char *)
+{
+  return 0;
+}
+
+/// Load 16 unaligned bytes at s
+inline __vector unsigned char simd_ldu(const char *s, simd_ldu_t)
+{
+  return vec_xl(0, reinterpret_cast<const unsigned char*>(s));
+}
+
+#else
+
+/// Load control for simd_ldu(), a vec_perm() control vector because VMX without VSX cannot load unaligned
+typedef __vector unsigned char simd_ldu_t;
+
+/// Obtain the load control to load 16 unaligned bytes at s and at any address congruent to s modulo 16,
+/// hoist this out of the loop: the loops advance s by a multiple of 16, so the alignment never changes
+inline simd_ldu_t simd_ldu_init(const char *s)
+{
+  return vec_lvsl(0, reinterpret_cast<const unsigned char*>(s));
+}
+
+/// Load 16 unaligned bytes at s with a load control obtained with simd_ldu_init() for the same alignment
+inline __vector unsigned char simd_ldu(const char *s, simd_ldu_t ctl)
+{
+  const unsigned char *p = reinterpret_cast<const unsigned char*>(s);
+  // vec_ld(15, p) loads the aligned 16 bytes holding p[15] and never touches a page that p[15] does not,
+  // when p is aligned it loads the same vector as vec_ld(0, p) and the vec_perm() is the identity
+  return vec_perm(vec_ld(0, p), vec_ld(15, p), ctl);
+}
+
+#endif
+
+} // namespace reflex
+
 #endif
 
 namespace reflex {
diff --git a/src/ugrep.cpp b/src/ugrep.cpp
index 9aa8793..b0efd16 100644
--- a/src/ugrep.cpp
+++ b/src/ugrep.cpp
@@ -15271,6 +15271,8 @@ void version()
     (reflex::have_HW_SSE2() ?  " +sse2" : " (no sse2!)") <<
 #elif defined(HAVE_NEON)
     " +neon/AArch64" <<
+#elif defined(HAVE_ALTIVEC)
+    " +altivec" <<
 #endif
 #if defined(HAVE_PCRE2)
     (pcre2_config(PCRE2_CONFIG_JIT, &tmp) >= 0 && tmp != 0 ? "; -P:pcre2jit" : "; -P:pcre2") <<
