Skip to content

Commit 7672e5e

Browse files
committed
crc32: add riscv64 implementation
1 parent 18af5ad commit 7672e5e

4 files changed

Lines changed: 132 additions & 0 deletions

File tree

.github/workflows/checks.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ jobs:
3131
# powerpc64le-unknown-linux-gnu
3232
CARGO_TARGET_POWERPC64LE_UNKNOWN_LINUX_GNU_LINKER: powerpc64le-linux-gnu-gcc
3333
CARGO_TARGET_POWERPC64LE_UNKNOWN_LINUX_GNU_RUNNER: qemu-ppc64le -L /usr/powerpc64le-linux-gnu
34+
# riscv64gc-unknown-linux-gnu
35+
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
36+
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER: qemu-riscv64 -L /usr/riscv64-linux-gnu -cpu rv64,zbc=true
37+
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-Ctarget-feature=+zbc"
3438
# wasm32-wasip1 (std for wasip2 is unstable)
3539
WASI_SDK_PATH: /tmp/wasi-sdk-24.0-x86_64-linux
3640
CC_wasm32_wasip1: /tmp/wasi-sdk-24.0-x86_64-linux/bin/clang
@@ -82,6 +86,11 @@ jobs:
8286
codecov: false
8387
packages: gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu qemu-user qemu-user-static
8488

89+
- target: "riscv64gc-unknown-linux-gnu"
90+
os: ubuntu-latest
91+
codecov: false
92+
packages: gcc-riscv64-linux-gnu g++-riscv64-linux-gnu qemu-user qemu-user-static
93+
8594
- target: "wasm32-wasip1"
8695
os: ubuntu-latest
8796
flags: "-p zlib-rs -p libz-rs-sys -p test-libz-rs-sys"

zlib-rs/src/cpu_features.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,11 @@ pub fn is_enabled_simd128() -> bool {
108108

109109
false
110110
}
111+
112+
#[inline(always)]
113+
pub fn is_enabled_zbc() -> bool {
114+
#[cfg(target_arch = "riscv64")]
115+
return cfg!(target_feature = "zbc");
116+
117+
false
118+
}

zlib-rs/src/crc32.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ mod pclmulqdq;
1313
#[cfg(target_arch = "x86_64")]
1414
#[cfg(feature = "vpclmulqdq")]
1515
mod vpclmulqdq;
16+
#[cfg(target_arch = "riscv64")]
17+
mod zbc;
1618

1719
pub use combine::{crc32_combine, crc32_combine_gen, crc32_combine_op};
1820

@@ -83,6 +85,12 @@ impl Crc32Fold {
8385
return;
8486
}
8587

88+
#[cfg(target_arch = "riscv64")]
89+
if crate::cpu_features::is_enabled_zbc() {
90+
self.value = unsafe { self::zbc::crc32_zbc_riscv64(self.value, src) };
91+
return;
92+
}
93+
8694
#[cfg(target_arch = "loongarch64")]
8795
{
8896
self.value = self::loongarch::crc32_loongarch64(self.value, src);

zlib-rs/src/crc32/zbc.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! crc32 implementation using the riscv64 zbc ISA extension. Derived from
2+
//! zlib-ng's implementation, see
3+
//! https://github.com/zlib-ng/zlib-ng/blob/da22434b657578c41af1bdf06b27304e4aceb00f/arch/riscv/crc32_zbc.c
4+
5+
use crate::crc32::zbc::asm::{clmul, clmulh};
6+
7+
use super::crc32_braid;
8+
9+
const CLMUL_MIN_LEN: usize = 16;
10+
const CLMUL_CHUNK_LEN: usize = 16;
11+
12+
const CONSTANT_R3: u64 = 0x1751997D0;
13+
const CONSTANT_R4: u64 = 0x0CCAA009E;
14+
const CONSTANT_R5: u64 = 0x163CD6124;
15+
const MASK32: u64 = 0xFFFFFFFF;
16+
const CRCPOLY_TRUE_LE_FULL: u64 = 0x1DB710641;
17+
const CONSTANT_RU: u64 = 0x1F7011641;
18+
19+
/// # Safety
20+
///
21+
/// This function must only be called on riscv64 with the zbc (carryless
22+
/// multiplication) feature.
23+
pub unsafe fn crc32_zbc_riscv64(mut crc: u32, buf: &[u8]) -> u32 {
24+
if buf.len() < CLMUL_MIN_LEN {
25+
return crc32_braid(crc, buf);
26+
}
27+
28+
let unaligned_len = buf.len() % CLMUL_CHUNK_LEN;
29+
if unaligned_len > 0 {
30+
crc = crc32_braid(crc, &buf[..unaligned_len]);
31+
}
32+
33+
!crc32_zbc_riscv64_impl(!crc, &buf[unaligned_len..])
34+
}
35+
36+
fn crc32_zbc_riscv64_impl(crc: u32, buf: &[u8]) -> u32 {
37+
// This unwrap is legal because crc32_zbc_riscv64 guarantees the input is at
38+
// least 16 bytes.
39+
let mut low = u64::from_le_bytes(buf[..8].try_into().unwrap()) ^ crc as u64;
40+
let mut high = u64::from_le_bytes(buf[8..16].try_into().unwrap());
41+
42+
buf.chunks_exact(16).skip(1).for_each(|chunk| {
43+
let t2 = clmul(CONSTANT_R4, high);
44+
let t3 = clmulh(CONSTANT_R4, high);
45+
let t0_new = clmul(CONSTANT_R3, low);
46+
let t1_new = clmulh(CONSTANT_R3, low);
47+
low = t0_new ^ t2;
48+
high = t1_new ^ t3;
49+
low ^= u64::from_le_bytes(chunk[..8].try_into().unwrap());
50+
high ^= u64::from_le_bytes(chunk[8..].try_into().unwrap());
51+
});
52+
53+
// Fold the 128-bit result into 64 bits
54+
let fold_t3 = clmulh(low, CONSTANT_R4);
55+
let fold_t2 = clmul(low, CONSTANT_R4);
56+
low = high ^ fold_t2;
57+
high = fold_t3;
58+
59+
// Combine the low and high parts and perform polynomial reduction
60+
let combined = (low >> 32) | ((high & MASK32) << 32);
61+
let reduced_low = { clmul(low & MASK32, CONSTANT_R5) } ^ combined;
62+
63+
// Barrett reduction step
64+
let mut barrett = clmul(reduced_low & MASK32, CONSTANT_RU) & MASK32;
65+
barrett = clmul(barrett, CRCPOLY_TRUE_LE_FULL);
66+
let ret = barrett ^ reduced_low;
67+
68+
(ret >> 32) as u32
69+
}
70+
71+
/// Inline assembly for required instructions, since the intrinsics are nightly
72+
/// compiler only.
73+
mod asm {
74+
// Returns the lower half of carryless multiplication of rs1 and rs2.
75+
// See https://riscv.github.io/riscv-isa-manual/snapshot/spec/#insns-clmul
76+
#[inline(always)]
77+
pub fn clmul(rs1: u64, rs2: u64) -> u64 {
78+
let rd;
79+
unsafe {
80+
core::arch::asm!(
81+
"clmul {rd}, {rs1}, {rs2}",
82+
rs1 = in(reg) rs1,
83+
rs2 = in(reg) rs2,
84+
rd = out(reg) rd,
85+
options(pure, nomem, nostack)
86+
);
87+
}
88+
rd
89+
}
90+
91+
// Returns the upper half of carryless multiplication of rs1 and rs2.
92+
// See https://riscv.github.io/riscv-isa-manual/snapshot/spec/#insns-clmulh
93+
#[inline(always)]
94+
pub fn clmulh(rs1: u64, rs2: u64) -> u64 {
95+
let rd;
96+
unsafe {
97+
core::arch::asm!(
98+
"clmulh {rd}, {rs1}, {rs2}",
99+
rs1 = in(reg) rs1,
100+
rs2 = in(reg) rs2,
101+
rd = out(reg) rd,
102+
options(pure, nomem, nostack)
103+
);
104+
}
105+
rd
106+
}
107+
}

0 commit comments

Comments
 (0)