// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0. //! Feature detection system for safe and efficient hardware acceleration across different //! platforms. #[cfg(all( not(feature = "std"), any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] use spin::Once; #[cfg(all( feature = "std", any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] use std::sync::OnceLock; #[cfg(all(feature = "alloc", not(feature = "std")))] extern crate alloc; #[cfg(all( feature = "alloc", not(feature = "std"), any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] use alloc::string::{String, ToString}; #[cfg(any( test, all( feature = "std", any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ) ))] use std::string::{String, ToString}; /// Global ArchOps instance cache - initialized once based on feature detection results #[cfg(all( feature = "std", any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] static ARCH_OPS_INSTANCE: OnceLock = OnceLock::new(); #[cfg(all( not(feature = "std"), any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] static ARCH_OPS_INSTANCE: Once = Once::new(); /// Performance tiers representing different hardware capability levels #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] // Some variants may not be constructed on all target architectures pub enum PerformanceTier { // AArch64 tiers AArch64AesSha3, AArch64Aes, // x86_64 tiers X86_64Avx512Vpclmulqdq, X86_64Avx512Pclmulqdq, X86_64SsePclmulqdq, // x86 tiers X86SsePclmulqdq, // Fallback SoftwareTable, } /// Architecture-specific capabilities #[derive(Debug, Clone, Copy)] #[allow(dead_code)] // Some fields may not be read on all target architectures pub struct ArchCapabilities { // AArch64 features pub has_aes: bool, // provides PMULL support for CRC calculations (NEON is implicit) pub has_crc: bool, // provides native CRC32 instructions for fusion techniques pub has_sha3: bool, // requires 'aes', provides EOR3 for XOR3 operations // x86/x86_64 features pub has_sse41: bool, pub has_sse42: bool, // provides native CRC32C instructions for fusion techniques pub has_pclmulqdq: bool, pub has_avx512vl: bool, // implicitly enables avx512f, has XOR3 operations pub has_vpclmulqdq: bool, } /// Helper function to convert a performance tier to a human-readable target string /// Format: {architecture}-{intrinsics-family}-{intrinsics-features} #[cfg(any( test, all( feature = "alloc", any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ) ))] #[inline(always)] fn tier_to_target_string(tier: PerformanceTier) -> String { match tier { PerformanceTier::AArch64AesSha3 => "aarch64-neon-pmull-sha3".to_string(), PerformanceTier::AArch64Aes => "aarch64-neon-pmull".to_string(), PerformanceTier::X86_64Avx512Vpclmulqdq => "x86_64-avx512-vpclmulqdq".to_string(), PerformanceTier::X86_64Avx512Pclmulqdq => "x86_64-avx512-pclmulqdq".to_string(), PerformanceTier::X86_64SsePclmulqdq => "x86_64-sse-pclmulqdq".to_string(), PerformanceTier::X86SsePclmulqdq => "x86-sse-pclmulqdq".to_string(), PerformanceTier::SoftwareTable => "software-fallback-tables".to_string(), } } /// Detect architecture-specific capabilities combining compile-time and runtime checks /// /// # Safety /// Uses runtime feature detection which may access CPU-specific registers #[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] unsafe fn detect_arch_capabilities() -> ArchCapabilities { #[cfg(target_arch = "aarch64")] { detect_aarch64_features() } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { detect_x86_features() } #[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))] { // Other architectures use software fallback ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, } } } /// AArch64-specific feature detection /// /// Note: NEON is always available on AArch64 and is implicitly enabled by AES support. /// AES support provides the PMULL instructions needed for CRC calculations. #[inline(always)] #[cfg(all(target_arch = "aarch64", feature = "std"))] unsafe fn detect_aarch64_features() -> ArchCapabilities { use std::arch::is_aarch64_feature_detected; // AES is available on essentially all AArch64 CPUs and provides the PMULL instructions let has_aes = is_aarch64_feature_detected!("aes"); // CRC provides native CRC32 instructions for fusion techniques let has_crc = is_aarch64_feature_detected!("crc"); // SHA3 is available on modern Aarch64 CPUs, and provides the EOR3 instruction for efficient // XOR3 operations. let has_sha3 = is_aarch64_feature_detected!("sha3"); ArchCapabilities { has_aes, has_crc, has_sha3, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, } } #[inline(always)] #[cfg(all(target_arch = "aarch64", not(feature = "std")))] unsafe fn detect_aarch64_features() -> ArchCapabilities { ArchCapabilities { has_aes: cfg!(target_feature = "aes"), has_crc: cfg!(target_feature = "crc"), has_sha3: cfg!(target_feature = "sha3"), has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, } } /// x86/x86_64-specific feature detection #[inline(always)] #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))] unsafe fn detect_x86_features() -> ArchCapabilities { use std::arch::is_x86_feature_detected; // SSE 4.1 and PCLMULQDQ support are the baseline for hardware acceleration let has_sse41 = is_x86_feature_detected!("sse4.1"); let has_pclmulqdq = has_sse41 && is_x86_feature_detected!("pclmulqdq"); let has_avx512vl = has_pclmulqdq && is_x86_feature_detected!("avx512vl"); let has_vpclmulqdq = has_avx512vl && is_x86_feature_detected!("vpclmulqdq"); // SSE 4.2 provides native CRC32C instructions for fusion techniques let has_sse42 = is_x86_feature_detected!("sse4.2"); ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41, has_sse42, has_pclmulqdq, has_avx512vl, has_vpclmulqdq, } } #[inline(always)] #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(feature = "std")))] unsafe fn detect_x86_features() -> ArchCapabilities { let has_sse41 = cfg!(target_feature = "sse4.1"); let has_sse42 = cfg!(target_feature = "sse4.2"); let has_pclmulqdq = has_sse41 && cfg!(target_feature = "pclmulqdq"); let has_avx512vl = has_pclmulqdq && cfg!(target_feature = "avx512vl"); let has_vpclmulqdq = has_avx512vl && cfg!(target_feature = "vpclmulqdq"); ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41, has_sse42, has_pclmulqdq, has_avx512vl, has_vpclmulqdq, } } /// Select the appropriate performance tier based on detected capabilities #[inline(always)] #[allow(unused)] pub(crate) fn select_performance_tier(capabilities: &ArchCapabilities) -> PerformanceTier { #[cfg(target_arch = "aarch64")] { if capabilities.has_sha3 && capabilities.has_aes { return PerformanceTier::AArch64AesSha3; } if capabilities.has_aes { return PerformanceTier::AArch64Aes; } } #[cfg(target_arch = "x86_64")] { if capabilities.has_vpclmulqdq { return PerformanceTier::X86_64Avx512Vpclmulqdq; } if capabilities.has_avx512vl { return PerformanceTier::X86_64Avx512Pclmulqdq; } if capabilities.has_pclmulqdq { return PerformanceTier::X86_64SsePclmulqdq; } } #[cfg(target_arch = "x86")] { if capabilities.has_pclmulqdq { return PerformanceTier::X86SsePclmulqdq; } } // Fallback to software implementation PerformanceTier::SoftwareTable } /// Enum that holds the different ArchOps implementations for compile-time dispatch /// This avoids the need for trait objects while still providing factory-based selection #[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] #[derive(Debug, Clone, Copy)] pub enum ArchOpsInstance { #[cfg(target_arch = "aarch64")] Aarch64Aes(crate::arch::aarch64::aes::Aarch64AesOps), #[cfg(target_arch = "aarch64")] Aarch64AesSha3(crate::arch::aarch64::aes_sha3::Aarch64AesSha3Ops), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] X86SsePclmulqdq(crate::arch::x86::sse::X86SsePclmulqdqOps), #[cfg(target_arch = "x86_64")] X86_64Avx512Pclmulqdq(crate::arch::x86_64::avx512::X86_64Avx512PclmulqdqOps), #[cfg(target_arch = "x86_64")] X86_64Avx512Vpclmulqdq(crate::arch::x86_64::avx512_vpclmulqdq::X86_64Avx512VpclmulqdqOps), /// Software fallback - no ArchOps struct needed SoftwareFallback, } #[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] impl ArchOpsInstance { #[inline(always)] pub fn get_tier(&self) -> PerformanceTier { match self { #[cfg(target_arch = "aarch64")] ArchOpsInstance::Aarch64Aes(_) => PerformanceTier::AArch64Aes, #[cfg(target_arch = "aarch64")] ArchOpsInstance::Aarch64AesSha3(_) => PerformanceTier::AArch64AesSha3, #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] ArchOpsInstance::X86SsePclmulqdq(_) => PerformanceTier::X86SsePclmulqdq, #[cfg(target_arch = "x86_64")] ArchOpsInstance::X86_64Avx512Pclmulqdq(_) => PerformanceTier::X86_64Avx512Pclmulqdq, #[cfg(target_arch = "x86_64")] ArchOpsInstance::X86_64Avx512Vpclmulqdq(_) => PerformanceTier::X86_64Avx512Vpclmulqdq, ArchOpsInstance::SoftwareFallback => PerformanceTier::SoftwareTable, } } /// Get a human-readable target string describing the active configuration #[cfg(all( feature = "alloc", any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] #[inline(always)] pub fn get_target_string(&self) -> String { tier_to_target_string(self.get_tier()) } } /// Get the global ArchOps instance (thread-safe, initialized once based on feature detection) /// /// This function provides access to the cached ArchOps instance that was selected based on /// feature detection results at library initialization time, eliminating runtime feature /// detection overhead from hot paths. #[cfg(all( feature = "std", any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] pub fn get_arch_ops() -> &'static ArchOpsInstance { ARCH_OPS_INSTANCE.get_or_init(create_arch_ops) } #[cfg(all( not(feature = "std"), any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] pub fn get_arch_ops() -> &'static ArchOpsInstance { ARCH_OPS_INSTANCE.call_once(create_arch_ops) } /// Factory function that creates the appropriate ArchOps struct based on cached feature detection /// /// This function uses the cached feature detection results to select the optimal /// architecture-specific implementation at library initialization time, eliminating /// runtime feature detection overhead from hot paths. #[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] fn create_arch_ops() -> ArchOpsInstance { let capabilities = unsafe { detect_arch_capabilities() }; let tier = select_performance_tier(&capabilities); create_arch_ops_from_tier(tier) } /// Helper function to create ArchOpsInstance from a performance tier #[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] fn create_arch_ops_from_tier(tier: PerformanceTier) -> ArchOpsInstance { match tier { #[cfg(target_arch = "aarch64")] PerformanceTier::AArch64AesSha3 => { use crate::arch::aarch64::aes_sha3::Aarch64AesSha3Ops; ArchOpsInstance::Aarch64AesSha3(Aarch64AesSha3Ops::new()) } #[cfg(target_arch = "aarch64")] PerformanceTier::AArch64Aes => { use crate::arch::aarch64::aes::Aarch64AesOps; ArchOpsInstance::Aarch64Aes(Aarch64AesOps) } #[cfg(target_arch = "x86_64")] PerformanceTier::X86_64Avx512Vpclmulqdq => { use crate::arch::x86_64::avx512_vpclmulqdq::X86_64Avx512VpclmulqdqOps; ArchOpsInstance::X86_64Avx512Vpclmulqdq(X86_64Avx512VpclmulqdqOps::new()) } #[cfg(target_arch = "x86_64")] PerformanceTier::X86_64Avx512Pclmulqdq => { use crate::arch::x86_64::avx512::X86_64Avx512PclmulqdqOps; ArchOpsInstance::X86_64Avx512Pclmulqdq(X86_64Avx512PclmulqdqOps::new()) } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] PerformanceTier::X86_64SsePclmulqdq | PerformanceTier::X86SsePclmulqdq => { create_x86_sse_pclmulqdq_ops() } PerformanceTier::SoftwareTable => { // Use software fallback ArchOpsInstance::SoftwareFallback } // Handle cases where the performance tier doesn't match the current architecture _ => { // This can happen when a tier is selected for a different architecture // Fall back to software implementation ArchOpsInstance::SoftwareFallback } } } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn create_x86_sse_pclmulqdq_ops() -> ArchOpsInstance { use crate::arch::x86::sse::X86SsePclmulqdqOps; ArchOpsInstance::X86SsePclmulqdq(X86SsePclmulqdqOps) } /// Test-specific tier selection that works across all architectures for comprehensive testing #[cfg(test)] pub fn select_performance_tier_for_test(capabilities: &ArchCapabilities) -> PerformanceTier { // AArch64 tier selection - SHA3 requires AES to be available if capabilities.has_sha3 && capabilities.has_aes { return PerformanceTier::AArch64AesSha3; } if capabilities.has_aes { return PerformanceTier::AArch64Aes; } // x86_64 tier selection - VPCLMULQDQ requires AVX512VL if capabilities.has_vpclmulqdq && capabilities.has_avx512vl { return PerformanceTier::X86_64Avx512Vpclmulqdq; } // AVX512VL requires PCLMULQDQ and SSE4.1 if capabilities.has_avx512vl && capabilities.has_pclmulqdq { return PerformanceTier::X86_64Avx512Pclmulqdq; } // PCLMULQDQ requires SSE4.1 if capabilities.has_pclmulqdq && capabilities.has_sse41 { return PerformanceTier::X86_64SsePclmulqdq; } // Fallback to software implementation PerformanceTier::SoftwareTable } #[cfg(test)] mod tests { use super::*; #[test] fn test_aarch64_tier_selection() { // Test that aarch64 tier selection follows the expected hierarchy // Test SHA3 + AES (highest tier) - NEON is implicit with AES let capabilities_sha3 = ArchCapabilities { has_aes: true, has_crc: false, has_sha3: true, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_sha3), PerformanceTier::AArch64AesSha3 ); // Test AES only (baseline tier) - NEON is implicit with AES let capabilities_aes = ArchCapabilities { has_aes: true, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_aes), PerformanceTier::AArch64Aes ); // Test missing AES (should fall back to software) let capabilities_no_aes = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_no_aes), PerformanceTier::SoftwareTable ); } #[test] fn test_aarch64_feature_hierarchy() { // Test that AArch64 feature hierarchy is properly maintained // NEON is always available on AArch64 and implicit with AES // AES provides PMULL instructions, SHA3 provides EOR3 // Create test capabilities with AES support (NEON is implicit) let capabilities_with_aes = ArchCapabilities { has_aes: true, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; // AES support means we have PMULL instructions available for CRC calculations assert!(capabilities_with_aes.has_aes); // SHA3 requires AES to be available first let capabilities_with_sha3 = ArchCapabilities { has_aes: true, has_crc: false, has_sha3: true, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert!(capabilities_with_sha3.has_aes); assert!(capabilities_with_sha3.has_sha3); } #[test] #[cfg(target_arch = "x86_64")] fn test_x86_64_tier_selection() { // Test that x86_64 tier selection follows the expected hierarchy // Test VPCLMULQDQ + AVX512 (highest tier) let capabilities_vpclmulqdq = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: true, has_vpclmulqdq: true, }; assert_eq!( select_performance_tier_for_test(&capabilities_vpclmulqdq), PerformanceTier::X86_64Avx512Vpclmulqdq ); // Test AVX512 + PCLMULQDQ (mid-tier) let capabilities_avx512 = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: true, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_avx512), PerformanceTier::X86_64Avx512Pclmulqdq ); // Test SSE + PCLMULQDQ (baseline tier) let capabilities_sse = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_sse), PerformanceTier::X86_64SsePclmulqdq ); // Test missing PCLMULQDQ (should fall back to software) let capabilities_no_pclmul = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_no_pclmul), PerformanceTier::SoftwareTable ); } #[test] #[cfg(target_arch = "x86")] fn test_x86_tier_selection() { // Test that x86 (32-bit) tier selection works correctly // Test SSE + PCLMULQDQ (only available tier for x86) let capabilities_sse = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_sse), PerformanceTier::X86_64SsePclmulqdq ); // For x86 32-bit testing, we need a special case since AVX512VL indicates x86_64 // Create capabilities without AVX512VL to simulate x86 32-bit let capabilities_x86_sse = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: false, // No AVX512 on 32-bit x86 has_vpclmulqdq: false, }; // This should select x86_64 tier since we're testing the general case assert_eq!( select_performance_tier_for_test(&capabilities_x86_sse), PerformanceTier::X86_64SsePclmulqdq ); // Test missing PCLMULQDQ (should fall back to software) let capabilities_no_pclmul = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&capabilities_no_pclmul), PerformanceTier::SoftwareTable ); } #[test] fn test_x86_feature_hierarchy() { // Test that x86 feature hierarchy is properly maintained // SSE4.1 is required for PCLMULQDQ // AVX512VL requires PCLMULQDQ // VPCLMULQDQ requires AVX512VL // Test feature dependencies are enforced let capabilities_full = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: true, has_vpclmulqdq: true, }; // All x86 features should be available when hierarchy is satisfied assert!(capabilities_full.has_sse41); assert!(capabilities_full.has_pclmulqdq); assert!(capabilities_full.has_avx512vl); assert!(capabilities_full.has_vpclmulqdq); } // Mock tests for compile-time and runtime feature agreement scenarios mod mock_feature_agreement_tests { use super::*; #[test] fn test_feature_dependency_validation() { // Test that feature dependencies are properly validated // SHA3 without AES should not be possible let invalid_sha3_caps = ArchCapabilities { has_aes: false, // Missing required dependency has_crc: false, has_sha3: true, // This should be impossible in real detection has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; // Should fall back to software since AES is required for SHA3 assert_eq!( select_performance_tier_for_test(&invalid_sha3_caps), PerformanceTier::SoftwareTable ); // VPCLMULQDQ without AVX512VL should not be possible let invalid_vpclmul_caps = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: false, // Missing required dependency has_vpclmulqdq: true, // This should be impossible in real detection }; // Should fall back to SSE tier since AVX512VL is required for VPCLMULQDQ assert_eq!( select_performance_tier_for_test(&invalid_vpclmul_caps), PerformanceTier::X86_64SsePclmulqdq ); } } // Comprehensive tier selection tests across different hardware configurations mod tier_selection_comprehensive_tests { use super::*; #[test] fn test_all_aarch64_tier_combinations() { // Test all possible AArch64 capability combinations // No features - software fallback let no_features = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&no_features), PerformanceTier::SoftwareTable ); // AES only - baseline AArch64 tier let aes_only = ArchCapabilities { has_aes: true, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&aes_only), PerformanceTier::AArch64Aes ); // AES + SHA3 - highest AArch64 tier let aes_sha3 = ArchCapabilities { has_aes: true, has_crc: false, has_sha3: true, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&aes_sha3), PerformanceTier::AArch64AesSha3 ); } #[test] fn test_all_x86_64_tier_combinations() { // Test all possible x86_64 capability combinations // No features - software fallback let no_features = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&no_features), PerformanceTier::SoftwareTable ); // SSE4.1 only - software fallback (PCLMULQDQ required) let sse_only = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&sse_only), PerformanceTier::SoftwareTable ); // SSE4.1 + PCLMULQDQ - baseline x86_64 tier let sse_pclmul = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&sse_pclmul), PerformanceTier::X86_64SsePclmulqdq ); // SSE4.1 + PCLMULQDQ + AVX512VL - mid-tier let avx512_pclmul_new_rust = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: true, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&avx512_pclmul_new_rust), PerformanceTier::X86_64Avx512Pclmulqdq ); // All features - highest tier let all_features_new_rust = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: true, has_vpclmulqdq: true, }; assert_eq!( select_performance_tier_for_test(&all_features_new_rust), PerformanceTier::X86_64Avx512Vpclmulqdq ); } #[test] fn test_x86_32bit_tier_combinations() { // Test x86 (32-bit) capability combinations // No features - software fallback let no_features = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; assert_eq!( select_performance_tier_for_test(&no_features), PerformanceTier::SoftwareTable ); // SSE4.1 + PCLMULQDQ - for x86 32-bit testing, we expect x86_64 tier in our test function // since the test function doesn't distinguish between x86 and x86_64 architectures let sse_pclmul = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: false, // AVX512 not available on 32-bit x86 has_vpclmulqdq: false, }; // The test function will return x86_64 tier since it doesn't distinguish architectures assert_eq!( select_performance_tier_for_test(&sse_pclmul), PerformanceTier::X86_64SsePclmulqdq ); } #[test] fn test_target_string_consistency() { // Test that target strings are consistent with selected tiers let test_cases = [ (PerformanceTier::AArch64AesSha3, "aarch64-neon-pmull-sha3"), (PerformanceTier::AArch64Aes, "aarch64-neon-pmull"), ( PerformanceTier::X86_64Avx512Vpclmulqdq, "x86_64-avx512-vpclmulqdq", ), ( PerformanceTier::X86_64Avx512Pclmulqdq, "x86_64-avx512-pclmulqdq", ), (PerformanceTier::X86_64SsePclmulqdq, "x86_64-sse-pclmulqdq"), (PerformanceTier::X86SsePclmulqdq, "x86-sse-pclmulqdq"), (PerformanceTier::SoftwareTable, "software-fallback-tables"), ]; for (tier, expected_string) in test_cases { assert_eq!(tier_to_target_string(tier), expected_string); } } } // Tests for graceful degradation between performance tiers mod graceful_degradation_tests { use super::*; #[test] fn test_aarch64_degradation_path() { // Test the degradation path for AArch64: SHA3+AES -> AES -> Software // Start with highest tier capabilities let mut capabilities = ArchCapabilities { has_aes: true, has_crc: false, has_sha3: true, has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; // Should select highest tier assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::AArch64AesSha3 ); // Remove SHA3 - should degrade to AES tier capabilities.has_sha3 = false; assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::AArch64Aes ); // Remove AES - should degrade to software capabilities.has_aes = false; assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::SoftwareTable ); } #[test] fn test_x86_64_degradation_path() { // Test the degradation path for x86_64: VPCLMULQDQ -> AVX512 -> SSE -> Software // Start with highest tier capabilities let mut capabilities = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: true, has_vpclmulqdq: true, }; // Should select highest tier assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::X86_64Avx512Vpclmulqdq ); // Remove VPCLMULQDQ - should degrade to AVX512 tier capabilities.has_vpclmulqdq = false; assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::X86_64Avx512Pclmulqdq ); // Remove AVX512VL - should degrade to SSE tier capabilities.has_avx512vl = false; assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::X86_64SsePclmulqdq ); // Remove PCLMULQDQ - should degrade to software capabilities.has_pclmulqdq = false; assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::SoftwareTable ); // Remove SSE4.1 - should still be software (already at lowest tier) capabilities.has_sse41 = false; assert_eq!( select_performance_tier_for_test(&capabilities), PerformanceTier::SoftwareTable ); } #[test] fn test_partial_feature_availability() { // Test scenarios where only some features in a tier are available // AArch64: SHA3 available but AES not (impossible in real hardware, but test safety) let aarch64_partial = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: true, // This would be impossible in real detection has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; // Should fall back to software since AES is required for SHA3 assert_eq!( select_performance_tier_for_test(&aarch64_partial), PerformanceTier::SoftwareTable ); // x86_64: VPCLMULQDQ available but AVX512VL not (impossible in real hardware) let x86_64_partial = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, has_sse42: false, has_pclmulqdq: true, has_avx512vl: false, has_vpclmulqdq: true, // This would be impossible in real detection }; // Should fall back to SSE tier since AVX512VL is required for VPCLMULQDQ assert_eq!( select_performance_tier_for_test(&x86_64_partial), PerformanceTier::X86_64SsePclmulqdq ); } } } #[cfg(test)] mod software_fallback_tests { use super::*; #[test] fn test_aarch64_without_aes_falls_back_to_software() { // Test that AArch64 without AES support falls back to software implementation let capabilities_no_aes = ArchCapabilities { has_aes: false, // No AES support has_crc: false, has_sha3: false, // SHA3 requires AES, so also false has_sse41: false, has_sse42: false, has_pclmulqdq: false, has_avx512vl: false, has_vpclmulqdq: false, }; let tier = select_performance_tier_for_test(&capabilities_no_aes); assert_eq!( tier, PerformanceTier::SoftwareTable, "AArch64 without AES should fall back to software implementation" ); } #[test] fn test_x86_without_pclmulqdq_falls_back_to_software() { // Test that x86 without SSE4.1/PCLMULQDQ falls back to software implementation let capabilities_no_pclmul = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: true, // SSE4.1 available has_sse42: false, has_pclmulqdq: false, // But PCLMULQDQ not available has_avx512vl: false, has_vpclmulqdq: false, }; let tier = select_performance_tier_for_test(&capabilities_no_pclmul); assert_eq!( tier, PerformanceTier::SoftwareTable, "x86 without PCLMULQDQ should fall back to software implementation" ); // Test x86 without SSE4.1 let capabilities_no_sse = ArchCapabilities { has_aes: false, has_crc: false, has_sha3: false, has_sse41: false, // No SSE4.1 support has_sse42: false, has_pclmulqdq: false, // PCLMULQDQ requires SSE4.1 has_avx512vl: false, has_vpclmulqdq: false, }; let tier = select_performance_tier_for_test(&capabilities_no_sse); assert_eq!( tier, PerformanceTier::SoftwareTable, "x86 without SSE4.1 should fall back to software implementation" ); } #[test] fn test_conditional_compilation_coverage() { // Test that software fallback is properly conditionally compiled // This test ensures the conditional compilation logic is working correctly // Software fallback should be available when needed #[cfg(any( not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")), target_arch = "x86", all(target_arch = "aarch64", not(target_feature = "aes")) ))] { // Software fallback should be compiled in these cases use crate::arch::software; let _test_fn = software::update; // This test passes if it compiles successfully } // For x86_64, software fallback should not be needed since SSE4.1/PCLMULQDQ are always available // But it may still be compiled for testing purposes } }