OXYGENHUB
◆ switch-says oxygen / vendor / crc-fast / src / crc32 / fusion / aarch64 / mod.rs
10.8 KiB · Rust · last changed Strata: the cloud stores send through ureq, so every release target links 1 month ago source blame history raw
1
//! Provides CRC-32/ISCSI and CRC-32/ISO-HDLC calculations using a fusion of native CLMUL
2
//! instructions and native CRC calculation instructions on aarch64.
3
//!
4
//! https://dougallj.wordpress.com/2022/05/22/faster-crc32-on-the-apple-m1/
5
//!
6
//! Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
7
//! with the help of Claude.ai.
8
//!
9
//! Modified as necessary for this Rust implementation.
10
//!
11
//! MIT licensed.
12
13
#![cfg(target_arch = "aarch64")]
14
15
mod iscsi;
16
mod iso_hdlc;
17
18
use core::arch::aarch64::*;
19
#[cfg(feature = "std")]
20
use std::arch::is_aarch64_feature_detected;
21
22
use iscsi::crc_pmull::crc32_iscsi_v12e_v1;
23
use iscsi::crc_pmull_sha3::crc32_iscsi_eor3_v9s3x2e_s3;
24
use iso_hdlc::crc_pmull::crc32_iso_hdlc_v12e_v1;
25
use iso_hdlc::crc_pmull_sha3::crc32_iso_hdlc_eor3_v9s3x2e_s3;
26
27
#[inline(always)]
28
pub fn crc32_iscsi(crc: u32, data: &[u8]) -> u32 {
29
    let data_len = data.len();
30
31
    // there's some variance among different aarch64 CPUs (Apple Silicon, AWS Graviton, etc.), but
32
    // 127 bytes is the "small" threshold where this is generally faster
33
    if data_len < 128 {
34
        return unsafe { crc32_iscsi_small_fast(crc, data) };
35
    }
36
37
    #[cfg(feature = "std")]
38
    let has_sha3 = is_aarch64_feature_detected!("sha3");
39
    #[cfg(not(feature = "std"))]
40
    let has_sha3 = cfg!(target_feature = "sha3");
41
42
    if has_sha3 {
43
        unsafe { crc32_iscsi_aes_sha3(crc, data, data_len) }
44
    } else {
45
        unsafe { crc32_iscsi_aes(crc, data, data_len) }
46
    }
47
}
48
49
#[inline(always)]
50
pub fn crc32_iso_hdlc(crc: u32, data: &[u8]) -> u32 {
51
    let data_len = data.len();
52
53
    // there's some variance among different aarch64 CPUs (Apple Silicon, AWS Graviton, etc.), but
54
    // 127 bytes is the "small" threshold where this is generally faster
55
    if data_len < 128 {
56
        return unsafe { crc32_iso_hdlc_small_fast(crc, data) };
57
    }
58
59
    #[cfg(feature = "std")]
60
    let has_sha3 = is_aarch64_feature_detected!("sha3");
61
    #[cfg(not(feature = "std"))]
62
    let has_sha3 = cfg!(target_feature = "sha3");
63
64
    if has_sha3 {
65
        unsafe { crc32_iso_hdlc_aes_sha3(crc, data, data_len) }
66
    } else {
67
        unsafe { crc32_iso_hdlc_aes(crc, data, data_len) }
68
    }
69
}
70
71
#[inline]
72
#[target_feature(enable = "crc,aes,sha3")]
73
unsafe fn crc32_iscsi_aes_sha3(crc: u32, data: &[u8], data_len: usize) -> u32 {
74
    unsafe {
75
        const LARGE_BUFFER_THRESHOLD: usize = 1024;
76
77
        // Select implementation based on buffer size
78
        if data_len <= LARGE_BUFFER_THRESHOLD {
79
            crc32_iscsi_v12e_v1(crc, data.as_ptr(), data_len)
80
        } else {
81
            crc32_iscsi_eor3_v9s3x2e_s3(crc, data.as_ptr(), data_len)
82
        }
83
    }
84
}
85
86
#[inline]
87
#[target_feature(enable = "crc,aes")]
88
unsafe fn crc32_iscsi_aes(crc: u32, data: &[u8], data_len: usize) -> u32 {
89
    unsafe { crc32_iscsi_v12e_v1(crc, data.as_ptr(), data_len) }
90
}
91
92
#[inline]
93
#[target_feature(enable = "crc,aes,sha3")]
94
unsafe fn crc32_iso_hdlc_aes_sha3(crc: u32, data: &[u8], data_len: usize) -> u32 {
95
    unsafe {
96
        const LARGE_BUFFER_THRESHOLD: usize = 1024;
97
98
        // Select implementation based on buffer size
99
        if data_len <= LARGE_BUFFER_THRESHOLD {
100
            crc32_iso_hdlc_v12e_v1(crc, data.as_ptr(), data_len)
101
        } else {
102
            crc32_iso_hdlc_eor3_v9s3x2e_s3(crc, data.as_ptr(), data_len)
103
        }
104
    }
105
}
106
107
#[inline]
108
#[target_feature(enable = "crc,aes")]
109
unsafe fn crc32_iso_hdlc_aes(crc: u32, data: &[u8], data_len: usize) -> u32 {
110
    unsafe { crc32_iso_hdlc_v12e_v1(crc, data.as_ptr(), data_len) }
111
}
112
113
#[inline]
114
#[target_feature(enable = "aes")]
115
unsafe fn clmul_lo(a: uint64x2_t, b: uint64x2_t) -> uint64x2_t {
116
    // Polynomial multiply low parts - convert u128 result to uint64x2_t
117
    let result = vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 0));
118
    vreinterpretq_u64_p128(result)
119
}
120
121
#[inline]
122
#[target_feature(enable = "aes")]
123
unsafe fn clmul_hi(a: uint64x2_t, b: uint64x2_t) -> uint64x2_t {
124
    // Polynomial multiply high parts - convert u128 result to uint64x2_t
125
    let result = vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 1));
126
    vreinterpretq_u64_p128(result)
127
}
128
129
#[inline]
130
#[target_feature(enable = "aes")]
131
unsafe fn clmul_scalar(a: u32, b: u32) -> uint64x2_t {
132
    // Polynomial multiply scalars - convert u128 result to uint64x2_t
133
    let result = vmull_p64(a as u64, b as u64);
134
    vreinterpretq_u64_p128(result)
135
}
136
137
#[inline]
138
#[target_feature(enable = "aes")]
139
unsafe fn clmul_lo_and_xor(a: uint64x2_t, b: uint64x2_t, c: uint64x2_t) -> uint64x2_t {
140
    veorq_u64(clmul_lo(a, b), c)
141
}
142
143
#[inline]
144
#[target_feature(enable = "aes")]
145
unsafe fn clmul_hi_and_xor(a: uint64x2_t, b: uint64x2_t, c: uint64x2_t) -> uint64x2_t {
146
    veorq_u64(clmul_hi(a, b), c)
147
}
148
149
/// CRC-32/ISCSI calculation for small buffers (< 128 bytes) using unrolled native CRC instructions
150
#[inline]
151
#[target_feature(enable = "crc")]
152
pub unsafe fn crc32_iscsi_small_fast(mut crc: u32, data: &[u8]) -> u32 {
153
    let (prefix, aligned, suffix) = data.align_to::<u64>();
154
155
    // Process unaligned prefix bytes
156
    for &byte in prefix {
157
        crc = __crc32cb(crc, byte);
158
    }
159
160
    // Process aligned u64s with 8-way unrolling (64 bytes per iteration)
161
    let mut chunks = aligned.chunks_exact(8);
162
    for chunk in &mut chunks {
163
        crc = __crc32cd(crc, chunk[0]);
164
        crc = __crc32cd(crc, chunk[1]);
165
        crc = __crc32cd(crc, chunk[2]);
166
        crc = __crc32cd(crc, chunk[3]);
167
        crc = __crc32cd(crc, chunk[4]);
168
        crc = __crc32cd(crc, chunk[5]);
169
        crc = __crc32cd(crc, chunk[6]);
170
        crc = __crc32cd(crc, chunk[7]);
171
    }
172
173
    // Process remaining aligned u64s
174
    for &val in chunks.remainder() {
175
        crc = __crc32cd(crc, val);
176
    }
177
178
    // Process unaligned suffix bytes
179
    for &byte in suffix {
180
        crc = __crc32cb(crc, byte);
181
    }
182
183
    crc
184
}
185
186
/// CRC-32/ISO-HDLC calculation for small buffers (< 128 bytes) using unrolled native CRC instructions
187
#[inline]
188
#[target_feature(enable = "crc")]
189
pub unsafe fn crc32_iso_hdlc_small_fast(mut crc: u32, data: &[u8]) -> u32 {
190
    let (prefix, aligned, suffix) = data.align_to::<u64>();
191
192
    // Process unaligned prefix bytes
193
    for &byte in prefix {
194
        crc = __crc32b(crc, byte);
195
    }
196
197
    // Process aligned u64s with 8-way unrolling (64 bytes per iteration)
198
    let mut chunks = aligned.chunks_exact(8);
199
    for chunk in &mut chunks {
200
        crc = __crc32d(crc, chunk[0]);
201
        crc = __crc32d(crc, chunk[1]);
202
        crc = __crc32d(crc, chunk[2]);
203
        crc = __crc32d(crc, chunk[3]);
204
        crc = __crc32d(crc, chunk[4]);
205
        crc = __crc32d(crc, chunk[5]);
206
        crc = __crc32d(crc, chunk[6]);
207
        crc = __crc32d(crc, chunk[7]);
208
    }
209
210
    // Process remaining aligned u64s
211
    for &val in chunks.remainder() {
212
        crc = __crc32d(crc, val);
213
    }
214
215
    // Process unaligned suffix bytes
216
    for &byte in suffix {
217
        crc = __crc32b(crc, byte);
218
    }
219
220
    crc
221
}
222
223
#[cfg(test)]
224
mod tests {
225
    use super::*;
226
    use crate::test::consts::TEST_CHECK_STRING;
227
    use crc::{Crc, Table};
228
    use rand::{rng, Rng};
229
230
    const RUST_CRC32_ISO_HDLC: Crc<u32, Table<16>> =
231
        Crc::<u32, Table<16>>::new(&crc::CRC_32_ISO_HDLC);
232
233
    const RUST_CRC32_ISCSI: Crc<u32, Table<16>> = Crc::<u32, Table<16>>::new(&crc::CRC_32_ISCSI);
234
235
    #[test]
236
    fn test_crc32_iso_hdlc_check() {
237
        assert_eq!(
238
            crc32_iso_hdlc(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff,
239
            0xcbf43926
240
        );
241
    }
242
243
    #[test]
244
    fn test_crc32_iso_hdlc_small_all_lengths() {
245
        for len in 1..=255 {
246
            crc32_iso_hdlc_random(len)
247
        }
248
    }
249
250
    #[test]
251
    fn test_crc32_iso_hdlc_medium_lengths() {
252
        // Test each length from 256 to 1024, which should fold and include handling remainders
253
        for len in 256..=1024 {
254
            crc32_iso_hdlc_random(len)
255
        }
256
    }
257
258
    #[test]
259
    fn test_crc32_iso_hdlc_large_lengths() {
260
        // Test 1 MiB just before, at, and just after the folding boundaries
261
        for len in 1048575..1048577 {
262
            crc32_iso_hdlc_random(len)
263
        }
264
    }
265
266
    #[test]
267
    fn test_crc32_iscsi_check() {
268
        assert_eq!(
269
            crc32_iscsi(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff,
270
            0xe3069283
271
        );
272
    }
273
274
    #[test]
275
    fn test_crc32_iscsi_small_all_lengths() {
276
        for len in 1..=255 {
277
            crc32_iscsi_random(len);
278
        }
279
    }
280
281
    #[test]
282
    fn test_crc32_iscsi_medium_lengths() {
283
        // Test each length from 256 to 1024, which should fold and include handling remainders
284
        for len in 256..=1024 {
285
            crc32_iscsi_random(len);
286
        }
287
    }
288
289
    #[test]
290
    fn test_crc32_iscsi_large_lengths() {
291
        // Test 1 MiB just before, at, and just after the folding boundaries
292
        for len in 1048575..1048577 {
293
            crc32_iscsi_random(len);
294
        }
295
    }
296
297
    #[cfg(target_feature = "sha3")]
298
    fn crc32_iso_hdlc_random(len: usize) {
299
        let mut data = vec![0u8; len];
300
        rng().fill(&mut data[..]);
301
302
        let checksum = RUST_CRC32_ISO_HDLC.checksum(&data);
303
304
        assert_eq!(crc32_iso_hdlc(0xffffffff, &data) ^ 0xffffffff, checksum);
305
306
        unsafe {
307
            assert_eq!(
308
                crc32_iso_hdlc_eor3_v9s3x2e_s3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
309
                checksum
310
            );
311
312
            assert_eq!(
313
                crc32_iso_hdlc_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
314
                checksum
315
            );
316
        }
317
    }
318
319
    #[cfg(not(target_feature = "sha3"))]
320
    fn crc32_iso_hdlc_random(len: usize) {
321
        let mut data = vec![0u8; len];
322
        rng().fill(&mut data[..]);
323
324
        let checksum = RUST_CRC32_ISO_HDLC.checksum(&data);
325
326
        assert_eq!(crc32_iso_hdlc(0xffffffff, &data) ^ 0xffffffff, checksum);
327
328
        unsafe {
329
            assert_eq!(
330
                crc32_iso_hdlc_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
331
                checksum
332
            );
333
        }
334
    }
335
336
    #[cfg(target_feature = "sha3")]
337
    fn crc32_iscsi_random(len: usize) {
338
        let mut data = vec![0u8; len];
339
        rng().fill(&mut data[..]);
340
341
        let checksum = RUST_CRC32_ISCSI.checksum(&data);
342
343
        assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum);
344
345
        unsafe {
346
            assert_eq!(
347
                crc32_iscsi_eor3_v9s3x2e_s3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
348
                checksum
349
            );
350
351
            assert_eq!(
352
                crc32_iscsi_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
353
                checksum
354
            );
355
        }
356
    }
357
358
    #[cfg(not(target_feature = "sha3"))]
359
    fn crc32_iscsi_random(len: usize) {
360
        let mut data = vec![0u8; len];
361
        rng().fill(&mut data[..]);
362
363
        let checksum = RUST_CRC32_ISCSI.checksum(&data);
364
365
        assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum);
366
367
        unsafe {
368
            assert_eq!(
369
                crc32_iscsi_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
370
                checksum
371
            );
372
        }
373
    }
374
}