OXYGENHUB
◆ v0.4.3 oxygen / vendor / crc-fast / src / feature_detection.rs
39.6 KiB · Rust · last changed Strata: the cloud stores send through ureq, so every release target links 1 month ago source blame history raw
2cd40ea1 Andy McCall ↶ 1
// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0.
2
3
//! Feature detection system for safe and efficient hardware acceleration across different
4
//! platforms.
5
6
#[cfg(all(
7
    not(feature = "std"),
8
    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
9
))]
10
use spin::Once;
11
#[cfg(all(
12
    feature = "std",
13
    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
14
))]
15
use std::sync::OnceLock;
16
17
#[cfg(all(feature = "alloc", not(feature = "std")))]
18
extern crate alloc;
19
#[cfg(all(
20
    feature = "alloc",
21
    not(feature = "std"),
22
    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
23
))]
24
use alloc::string::{String, ToString};
25
#[cfg(any(
26
    test,
27
    all(
28
        feature = "std",
29
        any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
30
    )
31
))]
32
use std::string::{String, ToString};
33
34
/// Global ArchOps instance cache - initialized once based on feature detection results
35
#[cfg(all(
36
    feature = "std",
37
    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
38
))]
39
static ARCH_OPS_INSTANCE: OnceLock<ArchOpsInstance> = OnceLock::new();
40
#[cfg(all(
41
    not(feature = "std"),
42
    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
43
))]
44
static ARCH_OPS_INSTANCE: Once<ArchOpsInstance> = Once::new();
45
46
/// Performance tiers representing different hardware capability levels
47
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48
#[allow(dead_code)] // Some variants may not be constructed on all target architectures
49
pub enum PerformanceTier {
50
    // AArch64 tiers
51
    AArch64AesSha3,
52
    AArch64Aes,
53
54
    // x86_64 tiers
55
    X86_64Avx512Vpclmulqdq,
56
    X86_64Avx512Pclmulqdq,
57
    X86_64SsePclmulqdq,
58
59
    // x86 tiers
60
    X86SsePclmulqdq,
61
62
    // Fallback
63
    SoftwareTable,
64
}
65
66
/// Architecture-specific capabilities
67
#[derive(Debug, Clone, Copy)]
68
#[allow(dead_code)] // Some fields may not be read on all target architectures
69
pub struct ArchCapabilities {
70
    // AArch64 features
71
    pub has_aes: bool, // provides PMULL support for CRC calculations (NEON is implicit)
72
    pub has_crc: bool, // provides native CRC32 instructions for fusion techniques
73
    pub has_sha3: bool, // requires 'aes', provides EOR3 for XOR3 operations
74
75
    // x86/x86_64 features
76
    pub has_sse41: bool,
77
    pub has_sse42: bool, // provides native CRC32C instructions for fusion techniques
78
    pub has_pclmulqdq: bool,
79
    pub has_avx512vl: bool, // implicitly enables avx512f, has XOR3 operations
80
    pub has_vpclmulqdq: bool,
81
}
82
83
/// Helper function to convert a performance tier to a human-readable target string
84
/// Format: {architecture}-{intrinsics-family}-{intrinsics-features}
85
#[cfg(any(
86
    test,
87
    all(
88
        feature = "alloc",
89
        any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
90
    )
91
))]
92
#[inline(always)]
93
fn tier_to_target_string(tier: PerformanceTier) -> String {
94
    match tier {
95
        PerformanceTier::AArch64AesSha3 => "aarch64-neon-pmull-sha3".to_string(),
96
        PerformanceTier::AArch64Aes => "aarch64-neon-pmull".to_string(),
97
        PerformanceTier::X86_64Avx512Vpclmulqdq => "x86_64-avx512-vpclmulqdq".to_string(),
98
        PerformanceTier::X86_64Avx512Pclmulqdq => "x86_64-avx512-pclmulqdq".to_string(),
99
        PerformanceTier::X86_64SsePclmulqdq => "x86_64-sse-pclmulqdq".to_string(),
100
        PerformanceTier::X86SsePclmulqdq => "x86-sse-pclmulqdq".to_string(),
101
        PerformanceTier::SoftwareTable => "software-fallback-tables".to_string(),
102
    }
103
}
104
105
/// Detect architecture-specific capabilities combining compile-time and runtime checks
106
///
107
/// # Safety
108
/// Uses runtime feature detection which may access CPU-specific registers
109
#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))]
110
unsafe fn detect_arch_capabilities() -> ArchCapabilities {
111
    #[cfg(target_arch = "aarch64")]
112
    {
113
        detect_aarch64_features()
114
    }
115
116
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
117
    {
118
        detect_x86_features()
119
    }
120
121
    #[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))]
122
    {
123
        // Other architectures use software fallback
124
        ArchCapabilities {
125
            has_aes: false,
126
            has_crc: false,
127
            has_sha3: false,
128
            has_sse41: false,
129
            has_sse42: false,
130
            has_pclmulqdq: false,
131
            has_avx512vl: false,
132
            has_vpclmulqdq: false,
133
        }
134
    }
135
}
136
137
/// AArch64-specific feature detection
138
///
139
/// Note: NEON is always available on AArch64 and is implicitly enabled by AES support.
140
/// AES support provides the PMULL instructions needed for CRC calculations.
141
#[inline(always)]
142
#[cfg(all(target_arch = "aarch64", feature = "std"))]
143
unsafe fn detect_aarch64_features() -> ArchCapabilities {
144
    use std::arch::is_aarch64_feature_detected;
145
146
    // AES is available on essentially all AArch64 CPUs and provides the PMULL instructions
147
    let has_aes = is_aarch64_feature_detected!("aes");
148
149
    // CRC provides native CRC32 instructions for fusion techniques
150
    let has_crc = is_aarch64_feature_detected!("crc");
151
152
    // SHA3 is available on modern Aarch64 CPUs, and provides the EOR3 instruction for efficient
153
    // XOR3 operations.
154
    let has_sha3 = is_aarch64_feature_detected!("sha3");
155
156
    ArchCapabilities {
157
        has_aes,
158
        has_crc,
159
        has_sha3,
160
        has_sse41: false,
161
        has_sse42: false,
162
        has_pclmulqdq: false,
163
        has_avx512vl: false,
164
        has_vpclmulqdq: false,
165
    }
166
}
167
168
#[inline(always)]
169
#[cfg(all(target_arch = "aarch64", not(feature = "std")))]
170
unsafe fn detect_aarch64_features() -> ArchCapabilities {
171
    ArchCapabilities {
172
        has_aes: cfg!(target_feature = "aes"),
173
        has_crc: cfg!(target_feature = "crc"),
174
        has_sha3: cfg!(target_feature = "sha3"),
175
        has_sse41: false,
176
        has_sse42: false,
177
        has_pclmulqdq: false,
178
        has_avx512vl: false,
179
        has_vpclmulqdq: false,
180
    }
181
}
182
183
/// x86/x86_64-specific feature detection
184
#[inline(always)]
185
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
186
unsafe fn detect_x86_features() -> ArchCapabilities {
187
    use std::arch::is_x86_feature_detected;
188
189
    // SSE 4.1 and PCLMULQDQ support are the baseline for hardware acceleration
190
    let has_sse41 = is_x86_feature_detected!("sse4.1");
191
    let has_pclmulqdq = has_sse41 && is_x86_feature_detected!("pclmulqdq");
192
193
    let has_avx512vl = has_pclmulqdq && is_x86_feature_detected!("avx512vl");
194
    let has_vpclmulqdq = has_avx512vl && is_x86_feature_detected!("vpclmulqdq");
195
    // SSE 4.2 provides native CRC32C instructions for fusion techniques
196
    let has_sse42 = is_x86_feature_detected!("sse4.2");
197
198
    ArchCapabilities {
199
        has_aes: false,
200
        has_crc: false,
201
        has_sha3: false,
202
        has_sse41,
203
        has_sse42,
204
        has_pclmulqdq,
205
        has_avx512vl,
206
        has_vpclmulqdq,
207
    }
208
}
209
210
#[inline(always)]
211
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(feature = "std")))]
212
unsafe fn detect_x86_features() -> ArchCapabilities {
213
    let has_sse41 = cfg!(target_feature = "sse4.1");
214
    let has_sse42 = cfg!(target_feature = "sse4.2");
215
    let has_pclmulqdq = has_sse41 && cfg!(target_feature = "pclmulqdq");
216
    let has_avx512vl = has_pclmulqdq && cfg!(target_feature = "avx512vl");
217
    let has_vpclmulqdq = has_avx512vl && cfg!(target_feature = "vpclmulqdq");
218
219
    ArchCapabilities {
220
        has_aes: false,
221
        has_crc: false,
222
        has_sha3: false,
223
        has_sse41,
224
        has_sse42,
225
        has_pclmulqdq,
226
        has_avx512vl,
227
        has_vpclmulqdq,
228
    }
229
}
230
231
/// Select the appropriate performance tier based on detected capabilities
232
#[inline(always)]
233
#[allow(unused)]
234
pub(crate) fn select_performance_tier(capabilities: &ArchCapabilities) -> PerformanceTier {
235
    #[cfg(target_arch = "aarch64")]
236
    {
237
        if capabilities.has_sha3 && capabilities.has_aes {
238
            return PerformanceTier::AArch64AesSha3;
239
        }
240
241
        if capabilities.has_aes {
242
            return PerformanceTier::AArch64Aes;
243
        }
244
    }
245
246
    #[cfg(target_arch = "x86_64")]
247
    {
248
        if capabilities.has_vpclmulqdq {
249
            return PerformanceTier::X86_64Avx512Vpclmulqdq;
250
        }
251
        if capabilities.has_avx512vl {
252
            return PerformanceTier::X86_64Avx512Pclmulqdq;
253
        }
254
        if capabilities.has_pclmulqdq {
255
            return PerformanceTier::X86_64SsePclmulqdq;
256
        }
257
    }
258
259
    #[cfg(target_arch = "x86")]
260
    {
261
        if capabilities.has_pclmulqdq {
262
            return PerformanceTier::X86SsePclmulqdq;
263
        }
264
    }
265
266
    // Fallback to software implementation
267
    PerformanceTier::SoftwareTable
268
}
269
270
/// Enum that holds the different ArchOps implementations for compile-time dispatch
271
/// This avoids the need for trait objects while still providing factory-based selection
272
#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))]
273
#[derive(Debug, Clone, Copy)]
274
pub enum ArchOpsInstance {
275
    #[cfg(target_arch = "aarch64")]
276
    Aarch64Aes(crate::arch::aarch64::aes::Aarch64AesOps),
277
    #[cfg(target_arch = "aarch64")]
278
    Aarch64AesSha3(crate::arch::aarch64::aes_sha3::Aarch64AesSha3Ops),
279
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
280
    X86SsePclmulqdq(crate::arch::x86::sse::X86SsePclmulqdqOps),
281
    #[cfg(target_arch = "x86_64")]
282
    X86_64Avx512Pclmulqdq(crate::arch::x86_64::avx512::X86_64Avx512PclmulqdqOps),
283
    #[cfg(target_arch = "x86_64")]
284
    X86_64Avx512Vpclmulqdq(crate::arch::x86_64::avx512_vpclmulqdq::X86_64Avx512VpclmulqdqOps),
285
    /// Software fallback - no ArchOps struct needed
286
    SoftwareFallback,
287
}
288
289
#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))]
290
impl ArchOpsInstance {
291
    #[inline(always)]
292
    pub fn get_tier(&self) -> PerformanceTier {
293
        match self {
294
            #[cfg(target_arch = "aarch64")]
295
            ArchOpsInstance::Aarch64Aes(_) => PerformanceTier::AArch64Aes,
296
            #[cfg(target_arch = "aarch64")]
297
            ArchOpsInstance::Aarch64AesSha3(_) => PerformanceTier::AArch64AesSha3,
298
            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
299
            ArchOpsInstance::X86SsePclmulqdq(_) => PerformanceTier::X86SsePclmulqdq,
300
            #[cfg(target_arch = "x86_64")]
301
            ArchOpsInstance::X86_64Avx512Pclmulqdq(_) => PerformanceTier::X86_64Avx512Pclmulqdq,
302
            #[cfg(target_arch = "x86_64")]
303
            ArchOpsInstance::X86_64Avx512Vpclmulqdq(_) => PerformanceTier::X86_64Avx512Vpclmulqdq,
304
            ArchOpsInstance::SoftwareFallback => PerformanceTier::SoftwareTable,
305
        }
306
    }
307
308
    /// Get a human-readable target string describing the active configuration
309
    #[cfg(all(
310
        feature = "alloc",
311
        any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
312
    ))]
313
    #[inline(always)]
314
    pub fn get_target_string(&self) -> String {
315
        tier_to_target_string(self.get_tier())
316
    }
317
}
318
319
/// Get the global ArchOps instance (thread-safe, initialized once based on feature detection)
320
///
321
/// This function provides access to the cached ArchOps instance that was selected based on
322
/// feature detection results at library initialization time, eliminating runtime feature
323
/// detection overhead from hot paths.
324
#[cfg(all(
325
    feature = "std",
326
    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
327
))]
328
pub fn get_arch_ops() -> &'static ArchOpsInstance {
329
    ARCH_OPS_INSTANCE.get_or_init(create_arch_ops)
330
}
331
332
#[cfg(all(
333
    not(feature = "std"),
334
    any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
335
))]
336
pub fn get_arch_ops() -> &'static ArchOpsInstance {
337
    ARCH_OPS_INSTANCE.call_once(create_arch_ops)
338
}
339
340
/// Factory function that creates the appropriate ArchOps struct based on cached feature detection
341
///
342
/// This function uses the cached feature detection results to select the optimal
343
/// architecture-specific implementation at library initialization time, eliminating
344
/// runtime feature detection overhead from hot paths.
345
#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))]
346
fn create_arch_ops() -> ArchOpsInstance {
347
    let capabilities = unsafe { detect_arch_capabilities() };
348
    let tier = select_performance_tier(&capabilities);
349
350
    create_arch_ops_from_tier(tier)
351
}
352
353
/// Helper function to create ArchOpsInstance from a performance tier
354
#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))]
355
fn create_arch_ops_from_tier(tier: PerformanceTier) -> ArchOpsInstance {
356
    match tier {
357
        #[cfg(target_arch = "aarch64")]
358
        PerformanceTier::AArch64AesSha3 => {
359
            use crate::arch::aarch64::aes_sha3::Aarch64AesSha3Ops;
360
            ArchOpsInstance::Aarch64AesSha3(Aarch64AesSha3Ops::new())
361
        }
362
        #[cfg(target_arch = "aarch64")]
363
        PerformanceTier::AArch64Aes => {
364
            use crate::arch::aarch64::aes::Aarch64AesOps;
365
            ArchOpsInstance::Aarch64Aes(Aarch64AesOps)
366
        }
367
        #[cfg(target_arch = "x86_64")]
368
        PerformanceTier::X86_64Avx512Vpclmulqdq => {
369
            use crate::arch::x86_64::avx512_vpclmulqdq::X86_64Avx512VpclmulqdqOps;
370
            ArchOpsInstance::X86_64Avx512Vpclmulqdq(X86_64Avx512VpclmulqdqOps::new())
371
        }
372
        #[cfg(target_arch = "x86_64")]
373
        PerformanceTier::X86_64Avx512Pclmulqdq => {
374
            use crate::arch::x86_64::avx512::X86_64Avx512PclmulqdqOps;
375
            ArchOpsInstance::X86_64Avx512Pclmulqdq(X86_64Avx512PclmulqdqOps::new())
376
        }
377
        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
378
        PerformanceTier::X86_64SsePclmulqdq | PerformanceTier::X86SsePclmulqdq => {
379
            create_x86_sse_pclmulqdq_ops()
380
        }
381
        PerformanceTier::SoftwareTable => {
382
            // Use software fallback
383
            ArchOpsInstance::SoftwareFallback
384
        }
385
        // Handle cases where the performance tier doesn't match the current architecture
386
        _ => {
387
            // This can happen when a tier is selected for a different architecture
388
            // Fall back to software implementation
389
            ArchOpsInstance::SoftwareFallback
390
        }
391
    }
392
}
393
394
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
395
fn create_x86_sse_pclmulqdq_ops() -> ArchOpsInstance {
396
    use crate::arch::x86::sse::X86SsePclmulqdqOps;
397
    ArchOpsInstance::X86SsePclmulqdq(X86SsePclmulqdqOps)
398
}
399
/// Test-specific tier selection that works across all architectures for comprehensive testing
400
#[cfg(test)]
401
pub fn select_performance_tier_for_test(capabilities: &ArchCapabilities) -> PerformanceTier {
402
    // AArch64 tier selection - SHA3 requires AES to be available
403
    if capabilities.has_sha3 && capabilities.has_aes {
404
        return PerformanceTier::AArch64AesSha3;
405
    }
406
407
    if capabilities.has_aes {
408
        return PerformanceTier::AArch64Aes;
409
    }
410
411
    // x86_64 tier selection - VPCLMULQDQ requires AVX512VL
412
    if capabilities.has_vpclmulqdq && capabilities.has_avx512vl {
413
        return PerformanceTier::X86_64Avx512Vpclmulqdq;
414
    }
415
416
    // AVX512VL requires PCLMULQDQ and SSE4.1
417
    if capabilities.has_avx512vl && capabilities.has_pclmulqdq {
418
        return PerformanceTier::X86_64Avx512Pclmulqdq;
419
    }
420
421
    // PCLMULQDQ requires SSE4.1
422
    if capabilities.has_pclmulqdq && capabilities.has_sse41 {
423
        return PerformanceTier::X86_64SsePclmulqdq;
424
    }
425
426
    // Fallback to software implementation
427
    PerformanceTier::SoftwareTable
428
}
429
430
#[cfg(test)]
431
mod tests {
432
    use super::*;
433
434
    #[test]
435
    fn test_aarch64_tier_selection() {
436
        // Test that aarch64 tier selection follows the expected hierarchy
437
438
        // Test SHA3 + AES (highest tier) - NEON is implicit with AES
439
        let capabilities_sha3 = ArchCapabilities {
440
            has_aes: true,
441
            has_crc: false,
442
            has_sha3: true,
443
            has_sse41: false,
444
            has_sse42: false,
445
            has_pclmulqdq: false,
446
            has_avx512vl: false,
447
            has_vpclmulqdq: false,
448
        };
449
        assert_eq!(
450
            select_performance_tier_for_test(&capabilities_sha3),
451
            PerformanceTier::AArch64AesSha3
452
        );
453
454
        // Test AES only (baseline tier) - NEON is implicit with AES
455
        let capabilities_aes = ArchCapabilities {
456
            has_aes: true,
457
            has_crc: false,
458
            has_sha3: false,
459
            has_sse41: false,
460
            has_sse42: false,
461
            has_pclmulqdq: false,
462
            has_avx512vl: false,
463
            has_vpclmulqdq: false,
464
        };
465
        assert_eq!(
466
            select_performance_tier_for_test(&capabilities_aes),
467
            PerformanceTier::AArch64Aes
468
        );
469
470
        // Test missing AES (should fall back to software)
471
        let capabilities_no_aes = ArchCapabilities {
472
            has_aes: false,
473
            has_crc: false,
474
            has_sha3: false,
475
            has_sse41: false,
476
            has_sse42: false,
477
            has_pclmulqdq: false,
478
            has_avx512vl: false,
479
            has_vpclmulqdq: false,
480
        };
481
        assert_eq!(
482
            select_performance_tier_for_test(&capabilities_no_aes),
483
            PerformanceTier::SoftwareTable
484
        );
485
    }
486
487
    #[test]
488
    fn test_aarch64_feature_hierarchy() {
489
        // Test that AArch64 feature hierarchy is properly maintained
490
        // NEON is always available on AArch64 and implicit with AES
491
        // AES provides PMULL instructions, SHA3 provides EOR3
492
493
        // Create test capabilities with AES support (NEON is implicit)
494
        let capabilities_with_aes = ArchCapabilities {
495
            has_aes: true,
496
            has_crc: false,
497
            has_sha3: false,
498
            has_sse41: false,
499
            has_sse42: false,
500
            has_pclmulqdq: false,
501
            has_avx512vl: false,
502
            has_vpclmulqdq: false,
503
        };
504
505
        // AES support means we have PMULL instructions available for CRC calculations
506
        assert!(capabilities_with_aes.has_aes);
507
508
        // SHA3 requires AES to be available first
509
        let capabilities_with_sha3 = ArchCapabilities {
510
            has_aes: true,
511
            has_crc: false,
512
            has_sha3: true,
513
            has_sse41: false,
514
            has_sse42: false,
515
            has_pclmulqdq: false,
516
            has_avx512vl: false,
517
            has_vpclmulqdq: false,
518
        };
519
520
        assert!(capabilities_with_sha3.has_aes);
521
        assert!(capabilities_with_sha3.has_sha3);
522
    }
523
524
    #[test]
525
    #[cfg(target_arch = "x86_64")]
526
    fn test_x86_64_tier_selection() {
527
        // Test that x86_64 tier selection follows the expected hierarchy
528
529
        // Test VPCLMULQDQ + AVX512 (highest tier)
530
        let capabilities_vpclmulqdq = ArchCapabilities {
531
            has_aes: false,
532
            has_crc: false,
533
            has_sha3: false,
534
            has_sse41: true,
535
            has_sse42: false,
536
            has_pclmulqdq: true,
537
            has_avx512vl: true,
538
            has_vpclmulqdq: true,
539
        };
540
        assert_eq!(
541
            select_performance_tier_for_test(&capabilities_vpclmulqdq),
542
            PerformanceTier::X86_64Avx512Vpclmulqdq
543
        );
544
545
        // Test AVX512 + PCLMULQDQ (mid-tier)
546
        let capabilities_avx512 = ArchCapabilities {
547
            has_aes: false,
548
            has_crc: false,
549
            has_sha3: false,
550
            has_sse41: true,
551
            has_sse42: false,
552
            has_pclmulqdq: true,
553
            has_avx512vl: true,
554
            has_vpclmulqdq: false,
555
        };
556
        assert_eq!(
557
            select_performance_tier_for_test(&capabilities_avx512),
558
            PerformanceTier::X86_64Avx512Pclmulqdq
559
        );
560
561
        // Test SSE + PCLMULQDQ (baseline tier)
562
        let capabilities_sse = ArchCapabilities {
563
            has_aes: false,
564
            has_crc: false,
565
            has_sha3: false,
566
            has_sse41: true,
567
            has_sse42: false,
568
            has_pclmulqdq: true,
569
            has_avx512vl: false,
570
            has_vpclmulqdq: false,
571
        };
572
        assert_eq!(
573
            select_performance_tier_for_test(&capabilities_sse),
574
            PerformanceTier::X86_64SsePclmulqdq
575
        );
576
577
        // Test missing PCLMULQDQ (should fall back to software)
578
        let capabilities_no_pclmul = ArchCapabilities {
579
            has_aes: false,
580
            has_crc: false,
581
            has_sha3: false,
582
            has_sse41: true,
583
            has_sse42: false,
584
            has_pclmulqdq: false,
585
            has_avx512vl: false,
586
            has_vpclmulqdq: false,
587
        };
588
        assert_eq!(
589
            select_performance_tier_for_test(&capabilities_no_pclmul),
590
            PerformanceTier::SoftwareTable
591
        );
592
    }
593
594
    #[test]
595
    #[cfg(target_arch = "x86")]
596
    fn test_x86_tier_selection() {
597
        // Test that x86 (32-bit) tier selection works correctly
598
599
        // Test SSE + PCLMULQDQ (only available tier for x86)
600
        let capabilities_sse = ArchCapabilities {
601
            has_aes: false,
602
            has_crc: false,
603
            has_sha3: false,
604
            has_sse41: true,
605
            has_sse42: false,
606
            has_pclmulqdq: true,
607
            has_avx512vl: false,
608
            has_vpclmulqdq: false,
609
        };
610
        assert_eq!(
611
            select_performance_tier_for_test(&capabilities_sse),
612
            PerformanceTier::X86_64SsePclmulqdq
613
        );
614
615
        // For x86 32-bit testing, we need a special case since AVX512VL indicates x86_64
616
        // Create capabilities without AVX512VL to simulate x86 32-bit
617
        let capabilities_x86_sse = ArchCapabilities {
618
            has_aes: false,
619
            has_crc: false,
620
            has_sha3: false,
621
            has_sse41: true,
622
            has_sse42: false,
623
            has_pclmulqdq: true,
624
            has_avx512vl: false, // No AVX512 on 32-bit x86
625
            has_vpclmulqdq: false,
626
        };
627
        // This should select x86_64 tier since we're testing the general case
628
        assert_eq!(
629
            select_performance_tier_for_test(&capabilities_x86_sse),
630
            PerformanceTier::X86_64SsePclmulqdq
631
        );
632
633
        // Test missing PCLMULQDQ (should fall back to software)
634
        let capabilities_no_pclmul = ArchCapabilities {
635
            has_aes: false,
636
            has_crc: false,
637
            has_sha3: false,
638
            has_sse41: true,
639
            has_sse42: false,
640
            has_pclmulqdq: false,
641
            has_avx512vl: false,
642
            has_vpclmulqdq: false,
643
        };
644
        assert_eq!(
645
            select_performance_tier_for_test(&capabilities_no_pclmul),
646
            PerformanceTier::SoftwareTable
647
        );
648
    }
649
650
    #[test]
651
    fn test_x86_feature_hierarchy() {
652
        // Test that x86 feature hierarchy is properly maintained
653
        // SSE4.1 is required for PCLMULQDQ
654
        // AVX512VL requires PCLMULQDQ
655
        // VPCLMULQDQ requires AVX512VL
656
657
        // Test feature dependencies are enforced
658
        let capabilities_full = ArchCapabilities {
659
            has_aes: false,
660
            has_crc: false,
661
            has_sha3: false,
662
            has_sse41: true,
663
            has_sse42: false,
664
            has_pclmulqdq: true,
665
            has_avx512vl: true,
666
            has_vpclmulqdq: true,
667
        };
668
669
        // All x86 features should be available when hierarchy is satisfied
670
        assert!(capabilities_full.has_sse41);
671
        assert!(capabilities_full.has_pclmulqdq);
672
        assert!(capabilities_full.has_avx512vl);
673
        assert!(capabilities_full.has_vpclmulqdq);
674
    }
675
676
    // Mock tests for compile-time and runtime feature agreement scenarios
677
    mod mock_feature_agreement_tests {
678
        use super::*;
679
680
        #[test]
681
        fn test_feature_dependency_validation() {
682
            // Test that feature dependencies are properly validated
683
684
            // SHA3 without AES should not be possible
685
            let invalid_sha3_caps = ArchCapabilities {
686
                has_aes: false, // Missing required dependency
687
                has_crc: false,
688
                has_sha3: true, // This should be impossible in real detection
689
                has_sse41: false,
690
                has_sse42: false,
691
                has_pclmulqdq: false,
692
                has_avx512vl: false,
693
                has_vpclmulqdq: false,
694
            };
695
696
            // Should fall back to software since AES is required for SHA3
697
            assert_eq!(
698
                select_performance_tier_for_test(&invalid_sha3_caps),
699
                PerformanceTier::SoftwareTable
700
            );
701
702
            // VPCLMULQDQ without AVX512VL should not be possible
703
            let invalid_vpclmul_caps = ArchCapabilities {
704
                has_aes: false,
705
                has_crc: false,
706
                has_sha3: false,
707
                has_sse41: true,
708
                has_sse42: false,
709
                has_pclmulqdq: true,
710
                has_avx512vl: false,  // Missing required dependency
711
                has_vpclmulqdq: true, // This should be impossible in real detection
712
            };
713
714
            // Should fall back to SSE tier since AVX512VL is required for VPCLMULQDQ
715
            assert_eq!(
716
                select_performance_tier_for_test(&invalid_vpclmul_caps),
717
                PerformanceTier::X86_64SsePclmulqdq
718
            );
719
        }
720
    }
721
722
    // Comprehensive tier selection tests across different hardware configurations
723
    mod tier_selection_comprehensive_tests {
724
        use super::*;
725
726
        #[test]
727
        fn test_all_aarch64_tier_combinations() {
728
            // Test all possible AArch64 capability combinations
729
730
            // No features - software fallback
731
            let no_features = ArchCapabilities {
732
                has_aes: false,
733
                has_crc: false,
734
                has_sha3: false,
735
                has_sse41: false,
736
                has_sse42: false,
737
                has_pclmulqdq: false,
738
                has_avx512vl: false,
739
                has_vpclmulqdq: false,
740
            };
741
            assert_eq!(
742
                select_performance_tier_for_test(&no_features),
743
                PerformanceTier::SoftwareTable
744
            );
745
746
            // AES only - baseline AArch64 tier
747
            let aes_only = ArchCapabilities {
748
                has_aes: true,
749
                has_crc: false,
750
                has_sha3: false,
751
                has_sse41: false,
752
                has_sse42: false,
753
                has_pclmulqdq: false,
754
                has_avx512vl: false,
755
                has_vpclmulqdq: false,
756
            };
757
            assert_eq!(
758
                select_performance_tier_for_test(&aes_only),
759
                PerformanceTier::AArch64Aes
760
            );
761
762
            // AES + SHA3 - highest AArch64 tier
763
            let aes_sha3 = ArchCapabilities {
764
                has_aes: true,
765
                has_crc: false,
766
                has_sha3: true,
767
                has_sse41: false,
768
                has_sse42: false,
769
                has_pclmulqdq: false,
770
                has_avx512vl: false,
771
                has_vpclmulqdq: false,
772
            };
773
            assert_eq!(
774
                select_performance_tier_for_test(&aes_sha3),
775
                PerformanceTier::AArch64AesSha3
776
            );
777
        }
778
779
        #[test]
780
        fn test_all_x86_64_tier_combinations() {
781
            // Test all possible x86_64 capability combinations
782
783
            // No features - software fallback
784
            let no_features = ArchCapabilities {
785
                has_aes: false,
786
                has_crc: false,
787
                has_sha3: false,
788
                has_sse41: false,
789
                has_sse42: false,
790
                has_pclmulqdq: false,
791
                has_avx512vl: false,
792
                has_vpclmulqdq: false,
793
            };
794
            assert_eq!(
795
                select_performance_tier_for_test(&no_features),
796
                PerformanceTier::SoftwareTable
797
            );
798
799
            // SSE4.1 only - software fallback (PCLMULQDQ required)
800
            let sse_only = ArchCapabilities {
801
                has_aes: false,
802
                has_crc: false,
803
                has_sha3: false,
804
                has_sse41: true,
805
                has_sse42: false,
806
                has_pclmulqdq: false,
807
                has_avx512vl: false,
808
                has_vpclmulqdq: false,
809
            };
810
            assert_eq!(
811
                select_performance_tier_for_test(&sse_only),
812
                PerformanceTier::SoftwareTable
813
            );
814
815
            // SSE4.1 + PCLMULQDQ - baseline x86_64 tier
816
            let sse_pclmul = ArchCapabilities {
817
                has_aes: false,
818
                has_crc: false,
819
                has_sha3: false,
820
                has_sse41: true,
821
                has_sse42: false,
822
                has_pclmulqdq: true,
823
                has_avx512vl: false,
824
                has_vpclmulqdq: false,
825
            };
826
            assert_eq!(
827
                select_performance_tier_for_test(&sse_pclmul),
828
                PerformanceTier::X86_64SsePclmulqdq
829
            );
830
831
            // SSE4.1 + PCLMULQDQ + AVX512VL - mid-tier
832
            let avx512_pclmul_new_rust = ArchCapabilities {
833
                has_aes: false,
834
                has_crc: false,
835
                has_sha3: false,
836
                has_sse41: true,
837
                has_sse42: false,
838
                has_pclmulqdq: true,
839
                has_avx512vl: true,
840
                has_vpclmulqdq: false,
841
            };
842
            assert_eq!(
843
                select_performance_tier_for_test(&avx512_pclmul_new_rust),
844
                PerformanceTier::X86_64Avx512Pclmulqdq
845
            );
846
847
            // All features - highest tier
848
            let all_features_new_rust = ArchCapabilities {
849
                has_aes: false,
850
                has_crc: false,
851
                has_sha3: false,
852
                has_sse41: true,
853
                has_sse42: false,
854
                has_pclmulqdq: true,
855
                has_avx512vl: true,
856
                has_vpclmulqdq: true,
857
            };
858
            assert_eq!(
859
                select_performance_tier_for_test(&all_features_new_rust),
860
                PerformanceTier::X86_64Avx512Vpclmulqdq
861
            );
862
        }
863
864
        #[test]
865
        fn test_x86_32bit_tier_combinations() {
866
            // Test x86 (32-bit) capability combinations
867
868
            // No features - software fallback
869
            let no_features = ArchCapabilities {
870
                has_aes: false,
871
                has_crc: false,
872
                has_sha3: false,
873
                has_sse41: false,
874
                has_sse42: false,
875
                has_pclmulqdq: false,
876
                has_avx512vl: false,
877
                has_vpclmulqdq: false,
878
            };
879
            assert_eq!(
880
                select_performance_tier_for_test(&no_features),
881
                PerformanceTier::SoftwareTable
882
            );
883
884
            // SSE4.1 + PCLMULQDQ - for x86 32-bit testing, we expect x86_64 tier in our test function
885
            // since the test function doesn't distinguish between x86 and x86_64 architectures
886
            let sse_pclmul = ArchCapabilities {
887
                has_aes: false,
888
                has_crc: false,
889
                has_sha3: false,
890
                has_sse41: true,
891
                has_sse42: false,
892
                has_pclmulqdq: true,
893
                has_avx512vl: false, // AVX512 not available on 32-bit x86
894
                has_vpclmulqdq: false,
895
            };
896
            // The test function will return x86_64 tier since it doesn't distinguish architectures
897
            assert_eq!(
898
                select_performance_tier_for_test(&sse_pclmul),
899
                PerformanceTier::X86_64SsePclmulqdq
900
            );
901
        }
902
903
        #[test]
904
        fn test_target_string_consistency() {
905
            // Test that target strings are consistent with selected tiers
906
907
            let test_cases = [
908
                (PerformanceTier::AArch64AesSha3, "aarch64-neon-pmull-sha3"),
909
                (PerformanceTier::AArch64Aes, "aarch64-neon-pmull"),
910
                (
911
                    PerformanceTier::X86_64Avx512Vpclmulqdq,
912
                    "x86_64-avx512-vpclmulqdq",
913
                ),
914
                (
915
                    PerformanceTier::X86_64Avx512Pclmulqdq,
916
                    "x86_64-avx512-pclmulqdq",
917
                ),
918
                (PerformanceTier::X86_64SsePclmulqdq, "x86_64-sse-pclmulqdq"),
919
                (PerformanceTier::X86SsePclmulqdq, "x86-sse-pclmulqdq"),
920
                (PerformanceTier::SoftwareTable, "software-fallback-tables"),
921
            ];
922
923
            for (tier, expected_string) in test_cases {
924
                assert_eq!(tier_to_target_string(tier), expected_string);
925
            }
926
        }
927
    }
928
929
    // Tests for graceful degradation between performance tiers
930
    mod graceful_degradation_tests {
931
        use super::*;
932
933
        #[test]
934
        fn test_aarch64_degradation_path() {
935
            // Test the degradation path for AArch64: SHA3+AES -> AES -> Software
936
937
            // Start with highest tier capabilities
938
            let mut capabilities = ArchCapabilities {
939
                has_aes: true,
940
                has_crc: false,
941
                has_sha3: true,
942
                has_sse41: false,
943
                has_sse42: false,
944
                has_pclmulqdq: false,
945
                has_avx512vl: false,
946
                has_vpclmulqdq: false,
947
            };
948
949
            // Should select highest tier
950
            assert_eq!(
951
                select_performance_tier_for_test(&capabilities),
952
                PerformanceTier::AArch64AesSha3
953
            );
954
955
            // Remove SHA3 - should degrade to AES tier
956
            capabilities.has_sha3 = false;
957
            assert_eq!(
958
                select_performance_tier_for_test(&capabilities),
959
                PerformanceTier::AArch64Aes
960
            );
961
962
            // Remove AES - should degrade to software
963
            capabilities.has_aes = false;
964
            assert_eq!(
965
                select_performance_tier_for_test(&capabilities),
966
                PerformanceTier::SoftwareTable
967
            );
968
        }
969
970
        #[test]
971
        fn test_x86_64_degradation_path() {
972
            // Test the degradation path for x86_64: VPCLMULQDQ -> AVX512 -> SSE -> Software
973
974
            // Start with highest tier capabilities
975
            let mut capabilities = ArchCapabilities {
976
                has_aes: false,
977
                has_crc: false,
978
                has_sha3: false,
979
                has_sse41: true,
980
                has_sse42: false,
981
                has_pclmulqdq: true,
982
                has_avx512vl: true,
983
                has_vpclmulqdq: true,
984
            };
985
986
            // Should select highest tier
987
            assert_eq!(
988
                select_performance_tier_for_test(&capabilities),
989
                PerformanceTier::X86_64Avx512Vpclmulqdq
990
            );
991
992
            // Remove VPCLMULQDQ - should degrade to AVX512 tier
993
            capabilities.has_vpclmulqdq = false;
994
            assert_eq!(
995
                select_performance_tier_for_test(&capabilities),
996
                PerformanceTier::X86_64Avx512Pclmulqdq
997
            );
998
999
            // Remove AVX512VL - should degrade to SSE tier
1000
            capabilities.has_avx512vl = false;
1001
            assert_eq!(
1002
                select_performance_tier_for_test(&capabilities),
1003
                PerformanceTier::X86_64SsePclmulqdq
1004
            );
1005
1006
            // Remove PCLMULQDQ - should degrade to software
1007
            capabilities.has_pclmulqdq = false;
1008
            assert_eq!(
1009
                select_performance_tier_for_test(&capabilities),
1010
                PerformanceTier::SoftwareTable
1011
            );
1012
1013
            // Remove SSE4.1 - should still be software (already at lowest tier)
1014
            capabilities.has_sse41 = false;
1015
            assert_eq!(
1016
                select_performance_tier_for_test(&capabilities),
1017
                PerformanceTier::SoftwareTable
1018
            );
1019
        }
1020
1021
        #[test]
1022
        fn test_partial_feature_availability() {
1023
            // Test scenarios where only some features in a tier are available
1024
1025
            // AArch64: SHA3 available but AES not (impossible in real hardware, but test safety)
1026
            let aarch64_partial = ArchCapabilities {
1027
                has_aes: false,
1028
                has_crc: false,
1029
                has_sha3: true, // This would be impossible in real detection
1030
                has_sse41: false,
1031
                has_sse42: false,
1032
                has_pclmulqdq: false,
1033
                has_avx512vl: false,
1034
                has_vpclmulqdq: false,
1035
            };
1036
            // Should fall back to software since AES is required for SHA3
1037
            assert_eq!(
1038
                select_performance_tier_for_test(&aarch64_partial),
1039
                PerformanceTier::SoftwareTable
1040
            );
1041
1042
            // x86_64: VPCLMULQDQ available but AVX512VL not (impossible in real hardware)
1043
            let x86_64_partial = ArchCapabilities {
1044
                has_aes: false,
1045
                has_crc: false,
1046
                has_sha3: false,
1047
                has_sse41: true,
1048
                has_sse42: false,
1049
                has_pclmulqdq: true,
1050
                has_avx512vl: false,
1051
                has_vpclmulqdq: true, // This would be impossible in real detection
1052
            };
1053
            // Should fall back to SSE tier since AVX512VL is required for VPCLMULQDQ
1054
            assert_eq!(
1055
                select_performance_tier_for_test(&x86_64_partial),
1056
                PerformanceTier::X86_64SsePclmulqdq
1057
            );
1058
        }
1059
    }
1060
}
1061
1062
#[cfg(test)]
1063
mod software_fallback_tests {
1064
    use super::*;
1065
    #[test]
1066
    fn test_aarch64_without_aes_falls_back_to_software() {
1067
        // Test that AArch64 without AES support falls back to software implementation
1068
        let capabilities_no_aes = ArchCapabilities {
1069
            has_aes: false, // No AES support
1070
            has_crc: false,
1071
            has_sha3: false, // SHA3 requires AES, so also false
1072
            has_sse41: false,
1073
            has_sse42: false,
1074
            has_pclmulqdq: false,
1075
            has_avx512vl: false,
1076
            has_vpclmulqdq: false,
1077
        };
1078
1079
        let tier = select_performance_tier_for_test(&capabilities_no_aes);
1080
        assert_eq!(
1081
            tier,
1082
            PerformanceTier::SoftwareTable,
1083
            "AArch64 without AES should fall back to software implementation"
1084
        );
1085
    }
1086
1087
    #[test]
1088
    fn test_x86_without_pclmulqdq_falls_back_to_software() {
1089
        // Test that x86 without SSE4.1/PCLMULQDQ falls back to software implementation
1090
        let capabilities_no_pclmul = ArchCapabilities {
1091
            has_aes: false,
1092
            has_crc: false,
1093
            has_sha3: false,
1094
            has_sse41: true, // SSE4.1 available
1095
            has_sse42: false,
1096
            has_pclmulqdq: false, // But PCLMULQDQ not available
1097
            has_avx512vl: false,
1098
            has_vpclmulqdq: false,
1099
        };
1100
1101
        let tier = select_performance_tier_for_test(&capabilities_no_pclmul);
1102
        assert_eq!(
1103
            tier,
1104
            PerformanceTier::SoftwareTable,
1105
            "x86 without PCLMULQDQ should fall back to software implementation"
1106
        );
1107
1108
        // Test x86 without SSE4.1
1109
        let capabilities_no_sse = ArchCapabilities {
1110
            has_aes: false,
1111
            has_crc: false,
1112
            has_sha3: false,
1113
            has_sse41: false, // No SSE4.1 support
1114
            has_sse42: false,
1115
            has_pclmulqdq: false, // PCLMULQDQ requires SSE4.1
1116
            has_avx512vl: false,
1117
            has_vpclmulqdq: false,
1118
        };
1119
1120
        let tier = select_performance_tier_for_test(&capabilities_no_sse);
1121
        assert_eq!(
1122
            tier,
1123
            PerformanceTier::SoftwareTable,
1124
            "x86 without SSE4.1 should fall back to software implementation"
1125
        );
1126
    }
1127
1128
    #[test]
1129
    fn test_conditional_compilation_coverage() {
1130
        // Test that software fallback is properly conditionally compiled
1131
        // This test ensures the conditional compilation logic is working correctly
1132
1133
        // Software fallback should be available when needed
1134
        #[cfg(any(
1135
            not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")),
1136
            target_arch = "x86",
1137
            all(target_arch = "aarch64", not(target_feature = "aes"))
1138
        ))]
1139
        {
1140
            // Software fallback should be compiled in these cases
1141
            use crate::arch::software;
1142
            let _test_fn = software::update;
1143
            // This test passes if it compiles successfully
1144
        }
1145
1146
        // For x86_64, software fallback should not be needed since SSE4.1/PCLMULQDQ are always available
1147
        // But it may still be compiled for testing purposes
1148
    }
1149
}