|
| 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