|
| 1 | +//! Folding polynomial constants for CRC32 (bit-reflected polynomial 0xEDB88320). |
| 2 | +//! Derived from the Apple zlib implementation in `crc32lt_arm64.s`. |
| 3 | +
|
| 4 | +use std::arch::aarch64::{ |
| 5 | + poly64x2_t, uint8x16_t, vcombine_p64, vcreate_p64, vdupq_n_u32, vdupq_n_u64, veorq_u8, |
| 6 | + vgetq_lane_p64, vgetq_lane_u32, vgetq_lane_u64, vmull_high_p64, vmull_p64, |
| 7 | + vreinterpretq_p64_u8, vreinterpretq_u32_u8, vreinterpretq_u64_u8, vreinterpretq_u8_p128, |
| 8 | + vreinterpretq_u8_u32, vreinterpretq_u8_u64, vsetq_lane_u32, vsetq_lane_u64, |
| 9 | +}; |
| 10 | + |
| 11 | +/// pmull v4.1q, v0.1d, k.1d — polynomial multiply using the low 64-bit lanes. |
| 12 | +#[inline] |
| 13 | +#[target_feature(enable = "neon")] |
| 14 | +#[target_feature(enable = "aes")] |
| 15 | +unsafe fn pmull_lo(a: uint8x16_t, k: poly64x2_t) -> uint8x16_t { |
| 16 | + unsafe { |
| 17 | + let a = vreinterpretq_p64_u8(a); |
| 18 | + vreinterpretq_u8_p128(vmull_p64(vgetq_lane_p64::<0>(a), vgetq_lane_p64::<0>(k))) |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +/// Polynomial multiply using the high 64-bit lanes. |
| 23 | +#[inline] |
| 24 | +#[target_feature(enable = "neon")] |
| 25 | +#[target_feature(enable = "aes")] |
| 26 | +unsafe fn pmull_hi(a: uint8x16_t, k: poly64x2_t) -> uint8x16_t { |
| 27 | + unsafe { vreinterpretq_u8_p128(vmull_high_p64(vreinterpretq_p64_u8(a), k)) } |
| 28 | +} |
| 29 | + |
| 30 | +/// Fold v0 into `next` using constant pair `k`. |
| 31 | +#[inline] |
| 32 | +#[target_feature(enable = "neon")] |
| 33 | +#[target_feature(enable = "aes")] |
| 34 | +unsafe fn fold1(v0: uint8x16_t, next: uint8x16_t, k: poly64x2_t) -> uint8x16_t { |
| 35 | + unsafe { |
| 36 | + let t = veorq_u8(pmull_lo(v0, k), next); |
| 37 | + veorq_u8(pmull_hi(v0, k), t) |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +/// CRC32 vectorized using PMULL. |
| 42 | +/// |
| 43 | +/// Implements the PCLMULQDQ-equivalent technique on AArch64 as described in |
| 44 | +/// Intel's "Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction". |
| 45 | +/// |
| 46 | +/// Derived from the Apple zlib implementation of crc32_little_aligned_vector. |
| 47 | +#[target_feature(enable = "neon")] |
| 48 | +#[target_feature(enable = "aes")] |
| 49 | +unsafe fn crc32_little_aligned_vector(crc: u32, mut buf: &[uint8x16_t]) -> u32 { |
| 50 | + // 4-way 512-bit folding: K1 = x^(512+64) mod P(x), K2 = x^512 mod P(x) |
| 51 | + let k12 = vcombine_p64(vcreate_p64(0x154442bd4), vcreate_p64(0x1c6e41596)); |
| 52 | + // 1-way 128-bit folding: K3 = x^(128+64) mod P(x), K4 = x^128 mod P(x) |
| 53 | + let k34 = vcombine_p64(vcreate_p64(0x1751997d0), vcreate_p64(0x0ccaa009e)); |
| 54 | + // 128-bit -> 64-bit reduction: K5 = x^96 mod P(x), K6 = x^64 mod P(x) |
| 55 | + let k56 = vcombine_p64(vcreate_p64(0x0ccaa009e), vcreate_p64(0x163cd6124)); |
| 56 | + // Barrett reduction: ux = floor(x^64 / P(x)), Px = P(x) |
| 57 | + let upx = vcombine_p64(vcreate_p64(0x1F7011641), vcreate_p64(0x1DB710641)); |
| 58 | + |
| 59 | + let mut v0 = buf[0]; |
| 60 | + buf = &buf[1..]; |
| 61 | + |
| 62 | + // Insert crc into lane 0 and XOR with the first vector. |
| 63 | + let zero_with_crc = unsafe { vreinterpretq_u8_u32(vsetq_lane_u32::<0>(crc, vdupq_n_u32(0))) }; |
| 64 | + v0 = unsafe { veorq_u8(zero_with_crc, v0) }; |
| 65 | + |
| 66 | + if !buf.is_empty() { |
| 67 | + if let [v1, v2, v3, rest @ ..] = buf { |
| 68 | + buf = rest; |
| 69 | + |
| 70 | + let mut v1 = *v1; |
| 71 | + let mut v2 = *v2; |
| 72 | + let mut v3 = *v3; |
| 73 | + |
| 74 | + // Fold 4 independent streams using K12. |
| 75 | + while let [n0, n1, n2, n3, rest @ ..] = buf { |
| 76 | + buf = rest; |
| 77 | + |
| 78 | + let t0 = unsafe { veorq_u8(pmull_lo(v0, k12), *n0) }; |
| 79 | + let t1 = unsafe { veorq_u8(pmull_lo(v1, k12), *n1) }; |
| 80 | + v0 = unsafe { veorq_u8(pmull_hi(v0, k12), t0) }; |
| 81 | + v1 = unsafe { veorq_u8(pmull_hi(v1, k12), t1) }; |
| 82 | + |
| 83 | + let t2 = unsafe { veorq_u8(pmull_lo(v2, k12), *n2) }; |
| 84 | + let t3 = unsafe { veorq_u8(pmull_lo(v3, k12), *n3) }; |
| 85 | + v2 = unsafe { veorq_u8(pmull_hi(v2, k12), t2) }; |
| 86 | + v3 = unsafe { veorq_u8(pmull_hi(v3, k12), t3) }; |
| 87 | + } |
| 88 | + |
| 89 | + // Merge the 4 streams into v0 using K34. |
| 90 | + v0 = unsafe { fold1(v0, v1, k34) }; |
| 91 | + v0 = unsafe { fold1(v0, v2, k34) }; |
| 92 | + v0 = unsafe { fold1(v0, v3, k34) }; |
| 93 | + } |
| 94 | + |
| 95 | + // Fold remaining single vectors using K34. |
| 96 | + for &next in buf { |
| 97 | + v0 = unsafe { fold1(v0, next, k34) }; |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + // Reduce 128-bit v0 down to 32-bit CRC. |
| 102 | + |
| 103 | + // Step 1: 128 -> 96 bits using K5 (i.e. x^96 mod P(x)). |
| 104 | + |
| 105 | + // Fold low 64 bits forward by 96. |
| 106 | + let v1 = unsafe { pmull_lo(v0, k56) }; |
| 107 | + // Shift high 64 bits to low position and XOR. |
| 108 | + let v2 = unsafe { |
| 109 | + let hi = vgetq_lane_u64::<1>(vreinterpretq_u64_u8(v0)); |
| 110 | + vreinterpretq_u8_u64(vsetq_lane_u64::<0>(hi, vdupq_n_u64(0))) |
| 111 | + }; |
| 112 | + v0 = unsafe { veorq_u8(v2, v1) }; |
| 113 | + |
| 114 | + // Extract the 96-bit result into the correct position for the K6 multiply. |
| 115 | + let v3 = unsafe { |
| 116 | + let u = vreinterpretq_u32_u8(v0); |
| 117 | + let s0 = vgetq_lane_u32::<0>(u); |
| 118 | + let s1 = vgetq_lane_u32::<1>(u); |
| 119 | + let s2 = vgetq_lane_u32::<2>(u); |
| 120 | + let v3 = vreinterpretq_u8_u32(vsetq_lane_u32::<2>(s0, vdupq_n_u32(0))); |
| 121 | + let u = vsetq_lane_u32::<0>(s1, u); |
| 122 | + let u = vsetq_lane_u32::<1>(s2, u); |
| 123 | + v0 = vreinterpretq_u8_u32(u); |
| 124 | + v3 |
| 125 | + }; |
| 126 | + |
| 127 | + // Step 2: 96 -> 64 bits using K6 (i.e. x^64 mod P(x)). |
| 128 | + v0 = unsafe { veorq_u8(pmull_hi(v3, k56), v0) }; |
| 129 | + |
| 130 | + // Barrett reduction: 64 -> 32 bits. |
| 131 | + // |
| 132 | + // T1 = floor(v0 / x^32) * ux |
| 133 | + // T2 = floor(T1 / x^32) * Px |
| 134 | + // CRC = (v0 XOR T2) mod x^32 |
| 135 | + |
| 136 | + // Isolate low 32 bits of v0 for T1, clear the other lanes. |
| 137 | + let floor_v0 = unsafe { |
| 138 | + let u = vreinterpretq_u32_u8(v0); |
| 139 | + vreinterpretq_u8_u32(vsetq_lane_u32::<0>(vgetq_lane_u32::<0>(u), vdupq_n_u32(0))) |
| 140 | + }; |
| 141 | + |
| 142 | + let t1 = unsafe { pmull_lo(floor_v0, upx) }; |
| 143 | + |
| 144 | + // Place low 32 bits of T1 into high lane where pmull_hi expects it. |
| 145 | + let floor_t1 = unsafe { |
| 146 | + let u3 = vreinterpretq_u32_u8(v0); |
| 147 | + let t = vgetq_lane_u32::<0>(vreinterpretq_u32_u8(t1)); |
| 148 | + vreinterpretq_u8_u32(vsetq_lane_u32::<2>(t, u3)) |
| 149 | + }; |
| 150 | + |
| 151 | + let t2 = unsafe { pmull_hi(floor_t1, upx) }; |
| 152 | + |
| 153 | + // Apply correction and extract the output. |
| 154 | + let crc = unsafe { veorq_u8(v0, t2) }; |
| 155 | + unsafe { (vgetq_lane_u64::<0>(vreinterpretq_u64_u8(crc)) >> 32) as u32 } |
| 156 | +} |
| 157 | + |
| 158 | +/// CRC32 using Apple's pmull-based vectorized algorithm. |
| 159 | +/// |
| 160 | +/// Requires the AES hardware feature (for 64-bit polynomial multiply). |
| 161 | +/// Falls back to the scalar table for unaligned prefix, short chunks, |
| 162 | +/// and trailing bytes. |
| 163 | +#[target_feature(enable = "aes")] |
| 164 | +pub unsafe fn crc32_pmull_aarch64(crc: u32, buf: &[u8]) -> u32 { |
| 165 | + let table = super::braid::get_crc_table(); |
| 166 | + let mut crc = !crc; |
| 167 | + let mut buf = buf; |
| 168 | + |
| 169 | + // SAFETY: [u8; 16] safely transmutes into uint8x16_t. |
| 170 | + let (before, mut middle, after) = unsafe { buf.align_to::<uint8x16_t>() }; |
| 171 | + |
| 172 | + // Align to 16 bytes using the CRC table |
| 173 | + for &byte in before { |
| 174 | + crc = (crc >> 8) ^ table[((crc ^ byte as u32) & 0xff) as usize]; |
| 175 | + } |
| 176 | + |
| 177 | + // Minimum 32 bytes needed; process in chunks up to 2^30 bytes. |
| 178 | + while let [_, _, ..] = middle { |
| 179 | + let k = Ord::min(middle.len(), 1 << 26); |
| 180 | + let (now, later) = middle.split_at(k); |
| 181 | + crc = unsafe { crc32_little_aligned_vector(crc, now) }; |
| 182 | + middle = later; |
| 183 | + } |
| 184 | + |
| 185 | + // Trailing bytes (< 32) processed with the CRC table |
| 186 | + for &byte in &buf[buf.len() - (middle.len() * 16 + after.len())..] { |
| 187 | + crc = (crc >> 8) ^ table[((crc ^ byte as u32) & 0xff) as usize]; |
| 188 | + } |
| 189 | + |
| 190 | + !crc |
| 191 | +} |
| 192 | + |
| 193 | +#[cfg(test)] |
| 194 | +mod tests { |
| 195 | + use super::*; |
| 196 | + quickcheck::quickcheck! { |
| 197 | + fn crc32_pmull_aarch64_is_crc32fast(v: Vec<u8>, start: u32) -> bool { |
| 198 | + if !crate::cpu_features::is_enabled_aes() { |
| 199 | + return true; |
| 200 | + } |
| 201 | + |
| 202 | + let mut h = crc32fast::Hasher::new_with_initial(start); |
| 203 | + h.update(&v); |
| 204 | + |
| 205 | + let a = unsafe { crc32_pmull_aarch64(start, &v) }; |
| 206 | + let b = h.finalize(); |
| 207 | + |
| 208 | + a == b |
| 209 | + } |
| 210 | + } |
| 211 | +} |
0 commit comments