Skip to content

Commit 3cdcd64

Browse files
6eanutFelix-Gong
authored andcommitted
MDEV-41271 Add RISC-V Zvbc accelerated CRC-32C implementation
Extend the RISC-V CRC-32C path with a Zvbc vector implementation: - mysys/CMakeLists.txt: detect Zvbc + RVV intrinsics under -march=rv64gc_zbb_zvbc; when available rebuild the new crc32c_riscv_zvbc.cc with that march (optional, Zbc unaffected); - mysys/crc32/crc32c_riscv_zvbc.cc (new): K-lane vector core -- 4 lanes x 128-bit folding, vlseg2e64 de-interleaved 64B loads, vclmul_vx broadcast constants, single-element vector CLMUL for the Barrett merge (no scalar Zbc instruction anywhere), bit-exact with the Zbc core (same fold constants k1..k4 and Barrett math); - adaptive VLEN: one e64m1 vector pair at VLEN>=256, two pairs at VLEN=128, so 128-bit cores execute the vector path at full width; - mysys/crc32/crc32c_riscv.cc + crc32c.cc: runtime dispatch via riscv_hwprobe -- Zbc is preferred when present (existing deployments keep the optimized scalar core); the Zvbc vector path accelerates cores that implement Zvbc but not scalar Zbc. Correctness: RFC 3720 + random/chained/boundary inputs bit-identical to slicing-by-4; official unittest/mysys/crc32-t.c 36/36. Performance on Spacemit X100 (k3, VLEN=256, gcc 14.3.0), vs inherited slicing-by-4 crc32c_slow (official slow path, same my_crc32c entry): len slow (MB/s) PR-B2 Zvbc (MB/s) vs slow 128 B 289 1147 4.0x 256 B 296 2105 7.1x 1 KiB 297 5466 18.5x 4 KiB 297 9116 30.7x 64 KiB 296 11398 38.7x Dispatch prefers the scalar Zbc path when Zbc is available, so existing Zbc deployments see no behavior change or regression; the vector path is selected on cores without scalar Zbc. No behavior change on non-riscv64 builds or toolchains without Zvbc (optional compile check). Review revisions requested on PR MariaDB#5746 (dr-m): - mysys/crc32/crc32c_riscv.cc: assemble the ZBC/ZVBC extension bits without a branch at both sites, dropping the redundant intermediate variable; replace `(void) hwprobe;` with `std::ignore = hwprobe;` and include <tuple>. <tuple> introduces no static constructor, so the ifunc resolver's load-time constraint is unaffected. - mysys/crc32/crc32c.cc: expand the comment above the Zbc preference with the structural reason -- the vector core deliberately reuses the scalar fold to stay bit-exact, so its vl is capped at 4 and a wider VLEN buys nothing; scalar Zbc is preferred because the two are level on large input and the scalar path is clearly ahead on small input. Comment-only. Assisted-by: YuanSheng:DeepSeek-V4-Flash Co-authored-by: Xiaofei Gong <gongxiaofei24@iscas.ac.cn> Signed-off-by: Jiakai Xu <xujiakai2025@iscas.ac.cn>
1 parent d45cf75 commit 3cdcd64

4 files changed

Lines changed: 348 additions & 3 deletions

File tree

‎mysys/CMakeLists.txt‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,35 @@ ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "riscv64|RISCV64")
185185
crc32/crc32c_riscv.cc crc32/crc32c_riscv_zbc.cc)
186186
SET_SOURCE_FILES_PROPERTIES(crc32/crc32c_riscv_zbc.cc PROPERTIES
187187
COMPILE_FLAGS "-march=rv64gc_zbc_zbb")
188+
189+
# Zvbc vector core (optional): separate TU compiled for the vector
190+
# extension; the runtime probe/dispatch stay in crc32c_riscv.cc.
191+
# Zbc is deliberately left out of the -march: this core is all-vector,
192+
# and with Zbc present GCC's -foptimize-crc (on by default at -O2 since
193+
# GCC 15) rewrites the bitwise fallback in that file into scalar clmul
194+
# instructions, which do not exist on the Zvbc-without-Zbc cores this
195+
# code is for.
196+
SET(SAVE_REQ_FLAGS2 "${CMAKE_REQUIRED_FLAGS}")
197+
SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -march=rv64gc_zbb_zvbc")
198+
CHECK_CXX_SOURCE_COMPILES("
199+
#include <riscv_vector.h>
200+
#include <stdint.h>
201+
uint64_t f(uint64_t a, const uint64_t *p, size_t vl) {
202+
vuint64m1_t v = __riscv_vle64_v_u64m1(p, vl);
203+
return __riscv_vmv_x_s_u64m1_u64(__riscv_vclmul_vx_u64m1(v, a, vl));
204+
}
205+
int main() { uint64_t x[4]={1,2,3,4}; return (int) f(5, x, 4); }
206+
" HAVE_RISCV_ZVBC)
207+
SET(CMAKE_REQUIRED_FLAGS "${SAVE_REQ_FLAGS2}")
208+
IF(HAVE_RISCV_ZVBC)
209+
MESSAGE(STATUS "RISC-V Zvbc detected: enabling vector CRC32C")
210+
SET(MYSYS_SOURCES ${MYSYS_SOURCES} crc32/crc32c_riscv_zvbc.cc)
211+
SET_SOURCE_FILES_PROPERTIES(crc32/crc32c_riscv_zvbc.cc PROPERTIES
212+
COMPILE_FLAGS "-march=rv64gc_zbb_zvbc")
213+
ADD_DEFINITIONS(-DHAVE_RISCV_ZVBC)
214+
ELSE()
215+
MESSAGE(STATUS "RISC-V Zvbc not available: using Zbc scalar CRC32C")
216+
ENDIF()
188217
ENDIF()
189218
ENDIF()
190219
ENDIF()

‎mysys/crc32/crc32c.cc‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,10 @@ extern "C" my_crc32_t crc32c_aarch64_available(void);
491491
extern "C" const char *crc32c_aarch64_impl(my_crc32_t);
492492
#elif defined HAVE_RISCV_ZBC
493493
extern "C" unsigned crc32c_riscv_zbc(unsigned, const void *, size_t);
494-
extern "C" int rv_zbc_supported(void *);
494+
# ifdef HAVE_RISCV_ZVBC
495+
extern "C" unsigned crc32c_riscv_zvbc(unsigned, const void *, size_t);
496+
# endif
497+
extern "C" unsigned rv_riscv_crc_ext(void *);
495498
extern "C" const char *crc32c_riscv_impl(my_crc32_t);
496499
#elif defined __i386__||defined __x86_64__||defined _M_X64||defined _M_IX86
497500
extern "C" my_crc32_t crc32c_x86_available(void);
@@ -501,15 +504,41 @@ extern "C" const char *crc32c_x86_impl(my_crc32_t);
501504
#if defined HAVE_RISCV_ZBC
502505
static my_crc32_t crc32c_riscv_choose(void *hwprobe)
503506
{
504-
return rv_zbc_supported(hwprobe) ? crc32c_riscv_zbc : crc32c_slow;
507+
unsigned ext= rv_riscv_crc_ext(hwprobe);
508+
/* Where scalar Zbc is available, prefer it over the Zvbc vector core.
509+
510+
The vector core does not buy extra parallelism here. To stay
511+
bit-exact with the scalar core it reuses exactly the same fold:
512+
four 128-bit lanes over a fixed 64-byte span, with the same
513+
constants k1..k4 and the same Barrett step. It therefore caps vl
514+
at 4 and folds 64 bytes per iteration however wide VLEN is, and it
515+
issues the same number of carry-less multiplies. Its only edge is
516+
a single de-interleaving segment load per 64 bytes, which on large
517+
input is offset by its per-call overhead (vsetvl, and injecting
518+
the CRC into lane 0 through a store/load round trip): on Spacemit
519+
X100 (VLEN=256) it ties the scalar fold at 64 KiB (11398 vs 11687
520+
MB/s) and loses on small input (128 B: 1147 vs 5540 MB/s,
521+
1 KiB: 5466 vs 10277 MB/s).
522+
523+
Its reason to exist is cores that implement Zvbc but not scalar
524+
Zbc, where the only alternative is the slicing-by-4 crc32c_slow.
525+
Where Zbc is present, selecting it would only touch the vector
526+
register file for no gain. */
527+
if (ext & 1)
528+
return crc32c_riscv_zbc;
529+
#ifdef HAVE_RISCV_ZVBC
530+
if (ext & 2) /* Zvbc only (no scalar Zbc) */
531+
return crc32c_riscv_zvbc;
532+
#endif
533+
return crc32c_slow;
505534
}
506535

507536
/* The RISC-V resolver. Unlike the other architectures, the implementation is
508537
selected by an indirect function instead of at the first call, because the
509538
target operating systems of RISC-V (Linux and FreeBSD) support that.
510539
511540
The dynamic linker calls this at load time, before main(), so it may only
512-
run code that is safe there. rv_zbc_supported() is: it uses the
541+
run code that is safe there. rv_riscv_crc_ext() is: it uses the
513542
riscv_hwprobe system call and nothing else.
514543
515544
The resolver must never return NULL. Whereas the *_available() functions

‎mysys/crc32/crc32c_riscv.cc‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ argument is NULL and the system call is made directly.
1616
*/
1717

1818
#include <stddef.h>
19+
#include <tuple>
1920
#include <unistd.h>
2021
#include <sys/syscall.h>
2122

@@ -37,6 +38,9 @@ struct riscv_hwprobe { long long key; unsigned long long value; };
3738
#ifndef RISCV_HWPROBE_EXT_ZBC
3839
# define RISCV_HWPROBE_EXT_ZBC (1ULL << 7)
3940
#endif
41+
#ifndef RISCV_HWPROBE_EXT_ZVBC
42+
# define RISCV_HWPROBE_EXT_ZVBC (1ULL << 18)
43+
#endif
4044

4145
#ifndef SYS_riscv_hwprobe
4246
# ifdef __NR_riscv_hwprobe
@@ -77,9 +81,45 @@ extern "C" int rv_zbc_supported(void *hwprobe)
7781
}
7882

7983
extern "C" unsigned crc32c_riscv_zbc(unsigned, const void *, size_t);
84+
#ifdef HAVE_RISCV_ZVBC
85+
extern "C" unsigned crc32c_riscv_zvbc(unsigned, const void *, size_t);
86+
#endif
87+
88+
/* CRC acceleration level for the callers' ifunc resolver:
89+
3 = Zbc + Zvbc (prefer Zbc; the optimized scalar fold ties the 4-lane
90+
vector core at VLEN 256 and wins below it),
91+
2 = Zvbc only (no scalar Zbc -- the vector core is the only
92+
accelerated path on such cores), 1 = Zbc only, 0 = none. */
93+
extern "C" unsigned rv_riscv_crc_ext(void *hwprobe)
94+
{
95+
struct riscv_hwprobe p;
96+
p.key= RISCV_HWPROBE_KEY_IMA_EXT_0;
97+
p.value= 0;
98+
#ifdef HAVE_SYS_HWPROBE_H
99+
if (hwprobe != NULL)
100+
{
101+
unsigned long long value= 0;
102+
if (__riscv_hwprobe_one(reinterpret_cast<__riscv_hwprobe_t>(hwprobe),
103+
RISCV_HWPROBE_KEY_IMA_EXT_0, &value) != 0)
104+
return 0;
105+
return ((value & RISCV_HWPROBE_EXT_ZBC) ? 1u : 0u) |
106+
((value & RISCV_HWPROBE_EXT_ZVBC) ? 2u : 0u);
107+
}
108+
#else
109+
std::ignore = hwprobe;
110+
#endif
111+
if (syscall(SYS_riscv_hwprobe, &p, (size_t) 1, (size_t) 0, NULL, 0) != 0)
112+
return 0;
113+
return ((p.value & RISCV_HWPROBE_EXT_ZBC) ? 1u : 0u) |
114+
((p.value & RISCV_HWPROBE_EXT_ZVBC) ? 2u : 0u);
115+
}
80116

81117
extern "C" const char *crc32c_riscv_impl(my_crc32_t c)
82118
{
119+
#ifdef HAVE_RISCV_ZVBC
120+
if (c == crc32c_riscv_zvbc)
121+
return "Using RISC-V Zvbc vector carry-less multiply instructions";
122+
#endif
83123
if (c == crc32c_riscv_zbc)
84124
return "Using RISC-V Zbc carry-less multiply instructions";
85125
return NULL;

0 commit comments

Comments
 (0)