This is an automated email from the ASF dual-hosted git repository.

chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new 0f820848 Add RISC-V Zvbc vector CRC32C acceleration (#3332)
0f820848 is described below

commit 0f820848e0f9fa5d9185d836443684f980755f58
Author: Felix-Gong <[email protected]>
AuthorDate: Wed Jul 8 11:43:55 2026 +0800

    Add RISC-V Zvbc vector CRC32C acceleration (#3332)
    
    * Add RISC-V Zvbc vector CRC32C acceleration
    
    Implement CRC32C using Zvbc vector carry-less multiplication
    (vclmul/vclmulh RVV intrinsics). Processes 4 lanes of 128-bit
    folding per iteration (64 bytes), with 4-to-1 lane reduction
    and Barrett reduction for finalization.
    
    - Add rv_crc32c_vclmul() using vclmul/vclmulh intrinsics
    - Add isZvbc() runtime detection via /proc/cpuinfo
    - Add WITH_RISCV_ZVBC cmake option
    - Fix macro guards: support __riscv_zvbc without __riscv_zbc
    - Zvbc preferred over Zbc in Choose_Extend()
    
    Signed-off-by: Xiaofei Gong <[email protected]>
    Signed-off-by: YuanSheng <[email protected]>
    
    * Address review comments: fix vl check, aliasing, isa matching, march flag
    
    - Add vl<2 fallback for VLEN<128 (prevents UB from uninitialized elements)
    - Use memcpy instead of uint8_t*-to-uint64_t* casts (strict-aliasing safe)
    - Tighten /proc/cpuinfo ISA matching to _zbc/_zvbc only (prevents _zvbc
      falsely matching zbc)
    - Add missing 'v' base extension in -march flag (rv64gcv_zbc_zvbc)
    
    Signed-off-by: Xiaofei Gong <[email protected]>
    Signed-off-by: YuanSheng <[email protected]>
    
    * Add ZVBC/ZBC support to config_brpc.sh
    
    Add --with-riscv-zvbc and --with-riscv-zbc options to config_brpc.sh
    for RISC-V CRC32C acceleration. This allows building with:
    - --with-riscv-zvbc: -march=rv64gcv_zbc_zvbc
    - --with-riscv-zbc: -march=rv64gc_zbc
    
    Usage:
      ./config_brpc.sh --with-riscv-zvbc --headers=/usr/include 
--libs=/usr/lib64
    
    Signed-off-by: Xiaofei Gong <[email protected]>
    Signed-off-by: YuanSheng <[email protected]>
    
    * Rename isZvbc/isZbc to snake_case for consistency
    
    Address review comment: rename camelCase functions to snake_case
    following Google C++ Style Guide.
    
    Signed-off-by: Xiaofei Gong <[email protected]>
    Signed-off-by: YuanSheng <[email protected]>
    
    ---------
    
    Signed-off-by: Xiaofei Gong <[email protected]>
    Signed-off-by: YuanSheng <[email protected]>
---
 CMakeLists.txt      |   5 +-
 config_brpc.sh      |  19 ++++-
 src/butil/crc32c.cc | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 248 insertions(+), 11 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index a3ebb855..f30859dd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -173,7 +173,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
     elseif((CMAKE_SYSTEM_PROCESSOR MATCHES "riscv64"))
         # RISC-V specific optimizations
         option(WITH_RISCV_ZBC "Enable RISC-V Zbc carry-less multiplication for 
CRC32C acceleration" OFF)
-        if(WITH_RISCV_ZBC)
+        option(WITH_RISCV_ZVBC "Enable RISC-V Zvbc vector carry-less 
multiplication for CRC32C acceleration" OFF)
+        if(WITH_RISCV_ZVBC)
+            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gcv_zbc_zvbc")
+        elseif(WITH_RISCV_ZBC)
             set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gc_zbc")
         else()
             set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gc")
diff --git a/config_brpc.sh b/config_brpc.sh
index 4526d218..1c059420 100755
--- a/config_brpc.sh
+++ b/config_brpc.sh
@@ -54,13 +54,15 @@ else
     LDD=ldd
 fi
 
-TEMP=`getopt -o v: --long 
headers:,libs:,cc:,cxx:,with-glog,with-thrift,with-rdma,with-mesalink,with-bthread-tracer,with-debug-bthread-sche-safety,with-debug-lock,with-asan,nodebugsymbols,werror
 -n 'config_brpc' -- "$@"`
+TEMP=`getopt -o v: --long 
headers:,libs:,cc:,cxx:,with-glog,with-thrift,with-rdma,with-mesalink,with-bthread-tracer,with-debug-bthread-sche-safety,with-debug-lock,with-asan,with-riscv-zvbc,with-riscv-zbc,nodebugsymbols,werror
 -n 'config_brpc' -- "$@"`
 WITH_GLOG=0
 WITH_THRIFT=0
 WITH_RDMA=0
 WITH_MESALINK=0
 WITH_BTHREAD_TRACER=0
 WITH_ASAN=0
+WITH_RISCV_ZVBC=0
+WITH_RISCV_ZBC=0
 BRPC_DEBUG_BTHREAD_SCHE_SAFETY=0
 DEBUGSYMBOLS=-g
 WERROR=
@@ -92,6 +94,8 @@ while true; do
         --with-debug-bthread-sche-safety ) BRPC_DEBUG_BTHREAD_SCHE_SAFETY=1; 
shift 1 ;;
         --with-debug-lock ) BRPC_DEBUG_LOCK=1; shift 1 ;;
         --with-asan) WITH_ASAN=1; shift 1 ;;
+        --with-riscv-zvbc) WITH_RISCV_ZVBC=1; shift 1 ;;
+        --with-riscv-zbc) WITH_RISCV_ZBC=1; shift 1 ;;
         --nodebugsymbols ) DEBUGSYMBOLS=; shift 1 ;;
         --werror ) WERROR=-Werror; shift 1 ;;
         -- ) shift; break ;;
@@ -539,12 +543,23 @@ fi
 append_to_output "CPPFLAGS=${CPPFLAGS}"
 append_to_output "# without the flag, linux+arm64 may crash due to folding on 
TLS.
 ifeq (\$(CC),gcc)
-  ifeq (\$(shell uname -p),aarch64) 
+  ifeq (\$(shell uname -p),aarch64)
     CPPFLAGS+=-fno-gcse
   endif
 endif
 "
 
+# RISC-V Zvbc/Zbc support
+if [ "$(uname -m)" = "riscv64" ]; then
+    if [ $WITH_RISCV_ZVBC != 0 ]; then
+        CXXFLAGS="${CXXFLAGS} -march=rv64gcv_zbc_zvbc"
+        print_success "RISC-V Zvbc enabled: -march=rv64gcv_zbc_zvbc"
+    elif [ $WITH_RISCV_ZBC != 0 ]; then
+        CXXFLAGS="${CXXFLAGS} -march=rv64gc_zbc"
+        print_success "RISC-V Zbc enabled: -march=rv64gc_zbc"
+    fi
+fi
+
 append_to_output "CXXFLAGS=${CXXFLAGS}"
 
 append_to_output "ifeq (\$(NEED_LIBPROTOC), 1)"
diff --git a/src/butil/crc32c.cc b/src/butil/crc32c.cc
index 7de07cf4..4b942eea 100644
--- a/src/butil/crc32c.cc
+++ b/src/butil/crc32c.cc
@@ -421,8 +421,11 @@ uint32_t ExtendImpl(uint32_t crc, const char* buf, size_t 
size) {
   return static_cast<uint32_t>(l ^ 0xffffffffu);
 }
 
-#if defined(__riscv) && (__riscv_xlen == 64) && defined(__riscv_zbc)
+#if defined(__riscv) && (__riscv_xlen == 64) && (defined(__riscv_zbc) || 
defined(__riscv_zvbc))
 #include <stdio.h>
+#if defined(__riscv_zvbc)
+#include <riscv_vector.h>
+#endif
 
 // RISC-V Zbc carry-less multiplication inline helpers
 static inline uint64_t rv_clmul(uint64_t a, uint64_t b) {
@@ -488,6 +491,7 @@ static const uint64_t crc32c_fold_const[4] 
__attribute__((aligned(16))) = {
 
 // Hardware-accelerated CRC32C using RISC-V Zbc carry-less multiplication.
 // Processes data in 64-byte chunks with 128-bit folding, then Barrett reduces.
+#if defined(__riscv_zbc)
 static uint32_t rv_crc32c_clmul(uint32_t crc, const char* buf, size_t len) {
   // Convert external CRC to internal register state
   crc ^= 0xFFFFFFFF;
@@ -580,9 +584,10 @@ static uint32_t rv_crc32c_clmul(uint32_t crc, const char* 
buf, size_t len) {
   // Convert internal register state to external CRC
   return c ^ 0xFFFFFFFF;
 }
+#endif  // __riscv_zbc
 
 // Runtime detection: check if RISC-V CPU supports Zbc extension
-static bool isZbc() {
+static bool is_zbc() {
   static const bool zbc_supported = []() {
     FILE* f = fopen("/proc/cpuinfo", "r");
     if (!f) return false;
@@ -592,7 +597,7 @@ static bool isZbc() {
       if (strstr(line, "isa") || strstr(line, "hart isa")) {
         char* colon = strchr(line, ':');
         if (colon) {
-          if (strstr(colon, "_zbc") || strstr(colon, "zbc")) {
+          if (strstr(colon, "_zbc")) {
             supported = true;
             break;
           }
@@ -604,8 +609,210 @@ static bool isZbc() {
   }();
   return zbc_supported;
 }
+
+#if defined(__riscv_zvbc)
+// Hardware-accelerated CRC32C using RISC-V Zvbc vector carry-less 
multiplication.
+// Uses RVV vclmul/vclmulh to process 2 lanes per vector operation (VLEN=128).
+// With VLEN=128, each vector register holds 2 x 64-bit elements.
+// 4 lanes are processed using 2 vector register pairs per clmul step.
+static uint32_t rv_crc32c_vclmul(uint32_t crc, const char* buf, size_t len) {
+  crc ^= 0xFFFFFFFF;
+
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(buf);
+  size_t n = len;
+
+  if (n < 64) {
+    return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF;
+  }
+
+  // Align to 16-byte boundary
+  uintptr_t mis = (uintptr_t)p & 0xF;
+  if (mis) {
+    size_t pre = 16 - mis;
+    if (pre > n) pre = n;
+    crc = rv_crc32c_bitwise(crc, p, pre);
+    p += pre;
+    n -= pre;
+    if (n < 64) {
+      return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF;
+    }
+  }
+
+  // Set up RVV for 64-bit elements: vl = min(VLEN/64, 2) = 2 for VLEN=128
+  // If VLEN < 128, vl will be 1 and the vector path cannot be used; fall back.
+  size_t vl = __riscv_vsetvl_e64m1(2);
+  if (vl < 2) {
+    return rv_crc32c_bitwise(crc, p, n) ^ 0xFFFFFFFF;
+  }
+
+  // Construct fold constant vectors: {k1, k2} and {k3, k4}
+  // Each element gets the appropriate constant for its position:
+  // element 0 (lo half) uses k1/k3, element 1 (hi half) uses k2/k4
+  uint64_t k12_arr[2] = { crc32c_fold_const[0], crc32c_fold_const[1] };
+  uint64_t k34_arr[2] = { crc32c_fold_const[2], crc32c_fold_const[3] };
+  vuint64m1_t k12_vec = __riscv_vle64_v_u64m1(k12_arr, vl);  // {k1, k2}
+  vuint64m1_t k34_vec = __riscv_vle64_v_u64m1(k34_arr, vl);  // {k3, k4}
+
+  // Load first 64 bytes into 4 vector registers.
+  // Each vector = one 128-bit lane: {lo_64, hi_64}
+  // Use memcpy to avoid strict-aliasing violations when loading uint8_t* as 
uint64_t*
+  uint64_t lane1_buf[2], lane2_buf[2], lane3_buf[2], lane4_buf[2];
+  memcpy(lane1_buf, p + 0,  16);
+  memcpy(lane2_buf, p + 16, 16);
+  memcpy(lane3_buf, p + 32, 16);
+  memcpy(lane4_buf, p + 48, 16);
+  vuint64m1_t lane1 = __riscv_vle64_v_u64m1(lane1_buf, vl);
+  vuint64m1_t lane2 = __riscv_vle64_v_u64m1(lane2_buf, vl);
+  vuint64m1_t lane3 = __riscv_vle64_v_u64m1(lane3_buf, vl);
+  vuint64m1_t lane4 = __riscv_vle64_v_u64m1(lane4_buf, vl);
+
+  // XOR CRC into element 0 of first lane
+  uint64_t tmp[2];
+  __riscv_vse64_v_u64m1(tmp, lane1, vl);
+  tmp[0] ^= (uint64_t)crc;
+  lane1 = __riscv_vle64_v_u64m1(tmp, vl);
+
+  p += 64;
+  n -= 64;
+
+  // Main loop: fold 64 bytes per iteration using vector carry-less multiply.
+  //
+  // For each 128-bit lane {lo, hi}, the fold computes:
+  //   new_lo = clmul(lo, k1) ^ clmul(hi, k2) ^ data_lo
+  //   new_hi = clmulh(lo, k1) ^ clmulh(hi, k2) ^ data_hi
+  //
+  // With k12_vec = {k1, k2} and element-wise vclmul:
+  //   vclmul(lane, k12_vec)  = {clmul(lo, k1), clmul(hi, k2)}   (lo halves of 
products)
+  //   vclmulh(lane, k12_vec) = {clmulh(lo, k1), clmulh(hi, k2)} (hi halves of 
products)
+  //
+  // The 128-bit XOR of (lo*k1) and (hi*k2) decomposes element-wise:
+  //   new_lo = clmul(lo,k1) ^ clmul(hi,k2) = vclmul[0] ^ vclmul[1]
+  //   new_hi = clmulh(lo,k1) ^ clmulh(hi,k2) = vclmulh[0] ^ vclmulh[1]
+  //
+  // So we need to XOR across elements. With VLEN=128 (2 elements), we use
+  // scalar extraction for the cross-element XOR since there's no vector
+  // permute instruction for just 2 elements that's more efficient.
+  while (n >= 64) {
+    uint64_t d1_buf[2], d2_buf[2], d3_buf[2], d4_buf[2];
+    memcpy(d1_buf, p + 0,  16);
+    memcpy(d2_buf, p + 16, 16);
+    memcpy(d3_buf, p + 32, 16);
+    memcpy(d4_buf, p + 48, 16);
+    vuint64m1_t d1 = __riscv_vle64_v_u64m1(d1_buf, vl);
+    vuint64m1_t d2 = __riscv_vle64_v_u64m1(d2_buf, vl);
+    vuint64m1_t d3 = __riscv_vle64_v_u64m1(d3_buf, vl);
+    vuint64m1_t d4 = __riscv_vle64_v_u64m1(d4_buf, vl);
+
+    // Fold each lane using vector clmul with {k1, k2}
+    uint64_t lo_r[2], hi_r[2], d_r[2];
+
+    // Lane 1
+    __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane1, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane1, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(d_r, d1, vl);
+    d_r[0] ^= lo_r[0] ^ lo_r[1];
+    d_r[1] ^= hi_r[0] ^ hi_r[1];
+    lane1 = __riscv_vle64_v_u64m1(d_r, vl);
+
+    // Lane 2
+    __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane2, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane2, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(d_r, d2, vl);
+    d_r[0] ^= lo_r[0] ^ lo_r[1];
+    d_r[1] ^= hi_r[0] ^ hi_r[1];
+    lane2 = __riscv_vle64_v_u64m1(d_r, vl);
+
+    // Lane 3
+    __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane3, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane3, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(d_r, d3, vl);
+    d_r[0] ^= lo_r[0] ^ lo_r[1];
+    d_r[1] ^= hi_r[0] ^ hi_r[1];
+    lane3 = __riscv_vle64_v_u64m1(d_r, vl);
+
+    // Lane 4
+    __riscv_vse64_v_u64m1(lo_r, __riscv_vclmul_vv_u64m1(lane4, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(hi_r, __riscv_vclmulh_vv_u64m1(lane4, k12_vec, vl), 
vl);
+    __riscv_vse64_v_u64m1(d_r, d4, vl);
+    d_r[0] ^= lo_r[0] ^ lo_r[1];
+    d_r[1] ^= hi_r[0] ^ hi_r[1];
+    lane4 = __riscv_vle64_v_u64m1(d_r, vl);
+
+    p += 64;
+    n -= 64;
+  }
+
+  // Reduce 4 lanes to 1 using {k3, k4}
+  // Same fold pattern: fold lane_a into lane_b
+  #define FOLD_INTO(dst, src) do { \
+    uint64_t _lo[2], _hi[2], _d[2]; \
+    __riscv_vse64_v_u64m1(_lo, __riscv_vclmul_vv_u64m1(src, k34_vec, vl), vl); 
\
+    __riscv_vse64_v_u64m1(_hi, __riscv_vclmulh_vv_u64m1(src, k34_vec, vl), 
vl); \
+    __riscv_vse64_v_u64m1(_d, dst, vl); \
+    _d[0] ^= _lo[0] ^ _lo[1]; \
+    _d[1] ^= _hi[0] ^ _hi[1]; \
+    dst = __riscv_vle64_v_u64m1(_d, vl); \
+  } while(0)
+
+  FOLD_INTO(lane2, lane1);  // lane2 = fold(lane1) ^ lane2
+  FOLD_INTO(lane3, lane2);  // lane3 = fold(lane2) ^ lane3
+  FOLD_INTO(lane4, lane3);  // lane4 = fold(lane3) ^ lane4
+  #undef FOLD_INTO
+
+  // Extract final 128-bit state from vector register
+  uint64_t final_state[2];
+  __riscv_vse64_v_u64m1(final_state, lane4, vl);
+  uint64_t x0 = final_state[0];
+  uint64_t x1 = final_state[1];
+
+  // Barrett reduction: 128-bit -> 32-bit CRC (scalar)
+  uint64_t t4 = rv_clmul(x0, RV_CRC32C_CONST_1);
+  uint64_t t3 = rv_clmulh(x0, RV_CRC32C_CONST_1);
+  uint64_t t1 = x1 ^ t4;
+  t4 = t1 & RV_CRC32_MASK32;
+  t1 >>= 32;
+  uint64_t t0 = rv_clmul(t4, RV_CRC32C_CONST_0);
+  t3 = (t3 << 32) ^ t1 ^ t0;
+
+  t4 = t3 & RV_CRC32_MASK32;
+  t4 = rv_clmul(t4, RV_CRC32C_CONST_QUO);
+  t4 &= RV_CRC32_MASK32;
+  t4 = rv_clmul(t4, RV_CRC32C_CONST_POLY);
+  t4 ^= t3;
+
+  uint32_t c = (uint32_t)((t4 >> 32) & RV_CRC32_MASK32);
+  if (n) {
+    c = rv_crc32c_bitwise(c, p, n);
+  }
+  return c ^ 0xFFFFFFFF;
+}
+
+// Runtime detection: check if RISC-V CPU supports Zvbc extension
+static bool is_zvbc() {
+  static const bool zvbc_supported = []() {
+    FILE* f = fopen("/proc/cpuinfo", "r");
+    if (!f) return false;
+    bool supported = false;
+    char line[1024];
+    while (fgets(line, sizeof(line), f)) {
+      if (strstr(line, "isa") || strstr(line, "hart isa")) {
+        char* colon = strchr(line, ':');
+        if (colon) {
+          if (strstr(colon, "_zvbc")) {
+            supported = true;
+            break;
+          }
+        }
+      }
+    }
+    fclose(f);
+    return supported;
+  }();
+  return zvbc_supported;
 }
-#endif  // __riscv && __riscv_xlen == 64
+#endif  // __riscv_zvbc
+
+#endif  // __riscv && __riscv_xlen == 64 && (__riscv_zbc || __riscv_zvbc)
 
 // Detect if SSE4.2 or not.
 #ifdef __SSE4_2__
@@ -629,10 +836,17 @@ static inline Function Choose_Extend() {
     return (Function)ExtendImpl<FastCRC32Functor>;
   }
 #endif
-#if defined(__riscv) && (__riscv_xlen == 64) && defined(__riscv_zbc)
-  if (isZbc()) {
+#if defined(__riscv) && (__riscv_xlen == 64) && (defined(__riscv_zbc) || 
defined(__riscv_zvbc))
+#if defined(__riscv_zvbc)
+  if (is_zvbc()) {
+    return (Function)rv_crc32c_vclmul;
+  }
+#endif
+#if defined(__riscv_zbc)
+  if (is_zbc()) {
     return (Function)rv_crc32c_clmul;
   }
+#endif
 #endif
   return (Function)ExtendImpl<SlowCRC32Functor>;
 }
@@ -641,8 +855,13 @@ bool IsFastCrc32Supported() {
 #ifdef __SSE4_2__
   if (isSSE42()) return true;
 #endif
-#if defined(__riscv) && (__riscv_xlen == 64) && defined(__riscv_zbc)
-  if (isZbc()) return true;
+#if defined(__riscv) && (__riscv_xlen == 64) && (defined(__riscv_zbc) || 
defined(__riscv_zvbc))
+#if defined(__riscv_zvbc)
+  if (is_zvbc()) return true;
+#endif
+#if defined(__riscv_zbc)
+  if (is_zbc()) return true;
+#endif
 #endif
   return false;
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to