From bd4df8f2f7b4594ab4a00c857c9ae5ffe255512d Mon Sep 17 00:00:00 2001 From: Sergey Fedorov 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 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) 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 +#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 +#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 +#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 +#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 # endif +#elif defined(HAVE_ALTIVEC) +# include +// altivec.h defines vector, pixel and bool as macros, which breaks C++ 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(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(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(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(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") <<