From 6efdd56f3686f124773aa13d9605d46767730393 Mon Sep 17 00:00:00 2001 From: Don MacAskill Date: Sat, 8 Nov 2025 17:02:51 -0800 Subject: [PATCH 1/5] WIP. Ensure `miri` passes on x86_64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `miri` detected a variety of issues on x86_64. This commit resolves them. It also excludes a few tests, such as those that use large data sets but have full underlying code coverage in other smaller tests, and tests that have isolation issues, such as file I/O, but also have code coverage in other tests. It doesn’t include `aarch64` support, since `miri` seems to be missing support for some required intrinsics, so that work is still TBD. All 103 supported tests pass. --- src/arch/mod.rs | 38 +++++----------- src/arch/software.rs | 86 ++++++++++++++++++------------------- src/cache.rs | 15 ++++++- src/crc32/fusion/x86/mod.rs | 50 +++++++++++++-------- src/ffi.rs | 27 +++++++++--- src/lib.rs | 63 +++++++++++++++++++++------ 6 files changed, 171 insertions(+), 108 deletions(-) diff --git a/src/arch/mod.rs b/src/arch/mod.rs index aef965d..b004162 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -113,13 +113,7 @@ pub(crate) unsafe fn update(state: u64, bytes: &[u8], params: CrcParams) -> u64 32 => algorithm::update::<_, Width32>(state as u32, bytes, params, ops) as u64, _ => panic!("Unsupported CRC width: {}", params.width), }, - ArchOpsInstance::SoftwareFallback => { - #[cfg(target_arch = "x86")] - crate::arch::x86_software_update(state, bytes, params); - - // This should never happen, but just in case - panic!("x86 features missing (SSE4.1 && PCLMULQDQ)"); - } + ArchOpsInstance::SoftwareFallback => crate::arch::software::update(state, bytes, params), } } @@ -139,27 +133,8 @@ pub(crate) unsafe fn update(state: u64, bytes: &[u8], params: CrcParams) -> u64 32 => algorithm::update::<_, Width32>(state as u32, bytes, params, ops) as u64, _ => panic!("Unsupported CRC width: {}", params.width), }, - ArchOpsInstance::SoftwareFallback => x86_software_update(state, bytes, params), - } -} - -#[inline(always)] -#[allow(unused)] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -fn x86_software_update(state: u64, bytes: &[u8], params: CrcParams) -> u64 { - if !is_x86_feature_detected!("sse4.1") || !is_x86_feature_detected!("pclmulqdq") { - #[cfg(all( - target_arch = "x86", - any(not(target_feature = "sse4.1"), not(target_feature = "pclmulqdq")) - ))] - { - // Use software implementation when no SIMD support is available - crate::arch::software::update(state, bytes, params); - } + ArchOpsInstance::SoftwareFallback => crate::arch::software::update(state, bytes, params), } - - // This should never happen, but just in case - panic!("x86 features missing (SSE4.1 && PCLMULQDQ)"); } #[inline] @@ -347,7 +322,10 @@ mod tests { } } + /// Skipping for Miri runs due to time constraints, underlying code already covered by other + /// tests. #[test] + #[cfg_attr(miri, ignore)] fn test_small_lengths_all() { // Test each CRC-64 variant for config in TEST_ALL_CONFIGS { @@ -358,7 +336,10 @@ mod tests { } } + /// Skipping for Miri runs due to time constraints, underlying code already covered by other + /// tests. #[test] + #[cfg_attr(miri, ignore)] fn test_medium_lengths() { // Test each CRC-64 variant for config in TEST_ALL_CONFIGS { @@ -369,7 +350,10 @@ mod tests { } } + /// Skipping for Miri runs due to time constraints, underlying code already covered by other + /// tests. #[test] + #[cfg_attr(miri, ignore)] fn test_large_lengths() { // Test each CRC-64 variant for config in TEST_ALL_CONFIGS { diff --git a/src/arch/software.rs b/src/arch/software.rs index 9cac286..52d9f7b 100644 --- a/src/arch/software.rs +++ b/src/arch/software.rs @@ -1,29 +1,13 @@ // Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0. //! This module contains a software fallback for unsupported architectures. -//! -//! Software fallback is conditionally compiled based on target architecture: -//! - Always included for non-SIMD architectures (not x86/x86_64/aarch64) -//! - Included for x86 when SSE4.1/PCLMULQDQ may not be available -//! - Included for aarch64 for runtime fallback when AES is not detected -//! - Excluded for x86_64 since SSE4.1/PCLMULQDQ are always available (but included for testing) - -#![cfg(any( - // Non-aarch64/x86/x86_64 architectures always need software fallback - not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")), - // x86 may not have SSE4.1/PCLMULQDQ support - all(target_arch = "x86", any(not(target_feature = "sse4.1"), not(target_feature = "pclmulqdq"))), - // aarch64 needs software fallback for runtime detection when AES is not available... - // NEON doesn't guarantee AES, so for rare outlier CPUs this might not work 100%... - all(target_arch = "aarch64", not(target_feature = "aes")), - // Include for testing on all architectures - test -))] use crate::consts::CRC_64_NVME; use crate::CrcAlgorithm; use crate::CrcParams; use crc::{Algorithm, Table}; +use std::collections::HashMap; +use std::sync::{Mutex, OnceLock}; #[allow(unused)] const RUST_CRC32_AIXM: crc::Crc> = @@ -96,6 +80,9 @@ const RUST_CRC64_WE: crc::Crc> = crc::Crc::>::new( #[allow(unused)] const RUST_CRC64_XZ: crc::Crc> = crc::Crc::>::new(&crc::CRC_64_XZ); +static CUSTOM_CRC32_CACHE: OnceLock>>> = OnceLock::new(); +static CUSTOM_CRC64_CACHE: OnceLock>>> = OnceLock::new(); + #[allow(unused)] // Dispatch function that handles the generic case pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { @@ -115,19 +102,25 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { CrcAlgorithm::Crc32Mpeg2 => RUST_CRC32_MPEG_2, CrcAlgorithm::Crc32Xfer => RUST_CRC32_XFER, CrcAlgorithm::Crc32Custom => { - let algorithm: Algorithm = Algorithm { - width: params.width, - poly: params.poly as u32, - init: params.init as u32, - refin: params.refin, - refout: params.refout, - xorout: params.xorout as u32, - check: params.check as u32, - residue: 0x00000000, // unused in this context - }; - - // ugly, but the crc crate is difficult to work with... - let static_algorithm = Box::leak(Box::new(algorithm)); + let cache = CUSTOM_CRC32_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + let mut cache = cache.lock().unwrap(); + + // Create a key from params that uniquely identifies this algorithm + let key = params.poly as u32; + + let static_algorithm = cache.entry(key).or_insert_with(|| { + let algorithm = Algorithm { + width: params.width, + poly: params.poly as u32, + init: params.init as u32, + refin: params.refin, + refout: params.refout, + xorout: params.xorout as u32, + check: params.check as u32, + residue: 0x00000000, + }; + Box::leak(Box::new(algorithm)) + }); crc::Crc::>::new(static_algorithm) } @@ -145,19 +138,24 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { CrcAlgorithm::Crc64We => RUST_CRC64_WE, CrcAlgorithm::Crc64Xz => RUST_CRC64_XZ, CrcAlgorithm::Crc64Custom => { - let algorithm: Algorithm = Algorithm { - width: params.width, - poly: params.poly, - init: params.init, - refin: params.refin, - refout: params.refout, - xorout: params.xorout, - check: params.check, - residue: 0x0000000000000000, // unused in this context - }; - - // ugly, but the crc crate is difficult to work with... - let static_algorithm = Box::leak(Box::new(algorithm)); + let cache = CUSTOM_CRC64_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + let mut cache = cache.lock().unwrap(); + + let key = params.poly; + + let static_algorithm = cache.entry(key).or_insert_with(|| { + let algorithm = Algorithm { + width: params.width, + poly: params.poly, + init: params.init, + refin: params.refin, + refout: params.refout, + xorout: params.xorout, + check: params.check, + residue: 0x0000000000000000, + }; + Box::leak(Box::new(algorithm)) + }); crc::Crc::>::new(static_algorithm) } diff --git a/src/cache.rs b/src/cache.rs index 8b1d73e..2aedea3 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1029,14 +1029,27 @@ mod tests { ); } + /// Non-stress version of the stress test to allow Miri to evaluate without timing out. #[test] + fn test_cache_memory_allocation_non_stress() { + cache_memory_allocation_stress(2); + } + + /// Skipping for Miri runs due to time constraints, underlying code already covered by other + /// tests. + #[test] + #[cfg_attr(miri, ignore)] fn test_cache_memory_allocation_stress() { + cache_memory_allocation_stress(1000); + } + + fn cache_memory_allocation_stress(count: i32) { clear_cache(); // Test cache behavior under memory allocation stress // Create a large number of unique cache entries to stress memory allocation let mut created_entries = Vec::new(); - let stress_count = 1000; + let stress_count = count; // Create many unique cache entries for i in 0..stress_count { diff --git a/src/crc32/fusion/x86/mod.rs b/src/crc32/fusion/x86/mod.rs index a8043b3..d5ae3d2 100644 --- a/src/crc32/fusion/x86/mod.rs +++ b/src/crc32/fusion/x86/mod.rs @@ -199,32 +199,40 @@ mod tests { #[test] fn test_crc32_iscsi_check() { - assert_eq!( - crc32_iscsi(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff, - 0xe3069283 - ); + if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { + assert_eq!( + crc32_iscsi(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff, + 0xe3069283 + ); + } } #[test] fn test_crc32_iscsi_small_all_lengths() { - for len in 1..=255 { - test_crc32_iscsi_random(len); + if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { + for len in 1..=255 { + test_crc32_iscsi_random(len); + } } } #[test] fn test_crc32_iscsi_medium_lengths() { - // Test each length from 256 to 1024, which should fold and include handling remainders - for len in 256..=1024 { - test_crc32_iscsi_random(len); + if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { + // Test each length from 256 to 1024, which should fold and include handling remainders + for len in 256..=1024 { + test_crc32_iscsi_random(len); + } } } #[test] fn test_crc32_iscsi_large_lengths() { - // Test 1 MiB just before, at, and just after the folding boundaries - for len in 1048575..1048577 { - test_crc32_iscsi_random(len); + if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { + // Test 1 MiB just before, at, and just after the folding boundaries + for len in 1048575..1048577 { + test_crc32_iscsi_random(len); + } } } @@ -235,7 +243,9 @@ mod tests { let checksum = RUST_CRC32_ISCSI.checksum(&data); - assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum); + if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { + assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum); + } unsafe { #[cfg(target_arch = "x86_64")] @@ -277,13 +287,15 @@ mod tests { let checksum = RUST_CRC32_ISCSI.checksum(&data); - assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum); + if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { + assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum); - unsafe { - assert_eq!( - crc32_iscsi_sse_v4s3x3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff, - checksum - ); + unsafe { + assert_eq!( + crc32_iscsi_sse_v4s3x3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff, + checksum + ); + } } } } diff --git a/src/ffi.rs b/src/ffi.rs index ab90391..6a32392 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -11,11 +11,13 @@ use crate::CrcAlgorithm; use crate::CrcParams; use crate::{get_calculator_target, Digest}; use std::collections::HashMap; +use std::collections::HashSet; use std::ffi::CStr; use std::os::raw::c_char; use std::slice; -use std::sync::Mutex; -use std::sync::OnceLock; +use std::sync::{Mutex, OnceLock}; + +static STRING_CACHE: OnceLock>> = OnceLock::new(); // Global storage for stable key pointers to ensure they remain valid across FFI boundary static STABLE_KEY_STORAGE: OnceLock>>> = OnceLock::new(); @@ -55,11 +57,12 @@ fn create_stable_key_pointer(keys: &crate::CrcKeysStorage) -> (*const u64, u32) }; let boxed_keys = key_vec.into_boxed_slice(); - let ptr = boxed_keys.as_ptr(); let count = boxed_keys.len() as u32; storage_map.insert(key_hash, boxed_keys); + let ptr = storage_map.get(&key_hash).expect("just inserted").as_ptr(); + (ptr, count) } @@ -473,8 +476,7 @@ pub extern "C" fn crc_fast_get_custom_params( // Get the custom params from the library let params = CrcParams::new( - // We need to use a static string for the name field - Box::leak(name.to_string().into_boxed_str()), + get_or_leak_string(name), // ✅ Use cached leak width, poly, init, @@ -536,3 +538,18 @@ unsafe fn convert_to_string(data: *const u8, len: usize) -> String { Err(_) => panic!("Invalid UTF-8 string"), } } + +fn get_or_leak_string(s: &str) -> &'static str { + let cache = STRING_CACHE.get_or_init(|| Mutex::new(HashSet::new())); + let mut cache = cache.lock().unwrap(); + + // Check if we already have this string + if let Some(&cached) = cache.get(s) { + return cached; + } + + // Leak it and cache the result + let leaked: &'static str = Box::leak(s.to_string().into_boxed_str()); + cache.insert(leaked); + leaked +} diff --git a/src/lib.rs b/src/lib.rs index e0f6795..15d60ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -864,16 +864,25 @@ fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) { /// fusion techniques to accelerate the calculation beyond what SIMD can do alone. #[inline(always)] fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 { + #[cfg(target_arch = "aarch64")] + { + use std::arch::is_aarch64_feature_detected; + if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") { + return fusion::crc32_iscsi(state as u32, data) as u64; + } + } + // both aarch64 and x86 have native CRC-32/ISCSI support, so we can use fusion - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))] - return fusion::crc32_iscsi(state as u32, data) as u64; - - #[cfg(all( - not(target_arch = "aarch64"), - not(target_arch = "x86_64"), - not(target_arch = "x86") - ))] - // Fallback to traditional calculation for other architectures + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + { + use std::arch::is_x86_feature_detected; + if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { + return fusion::crc32_iscsi(state as u32, data) as u64; + } + } + + // Fallback to traditional calculation for other architectures, which will eventually fall back + // to software tables if necessary Calculator::calculate(state, data, _params) } @@ -886,11 +895,15 @@ fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 { fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 { // aarch64 CPUs have native CRC-32/ISO-HDLC support, so we can use the fusion implementation #[cfg(target_arch = "aarch64")] - return fusion::crc32_iso_hdlc(state as u32, data) as u64; + { + use std::arch::is_aarch64_feature_detected; + if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("aes") { + return fusion::crc32_iso_hdlc(state as u32, data) as u64; + } + } // x86 CPUs don't have native CRC-32/ISO-HDLC support, so there's no fusion to be had, use - // traditional calculation - #[cfg(not(target_arch = "aarch64"))] + // traditional calculation, which will eventually fall back to software tables if necessary Calculator::calculate(state, data, _params) } @@ -1082,6 +1095,16 @@ mod lib { } #[test] + fn test_1024_length() { + for config in TEST_ALL_CONFIGS { + test_length(1024, config); + } + } + + /// Skipping for Miri runs due to time constraints, underlying code already covered by other + /// tests. + #[test] + #[cfg_attr(miri, ignore)] fn test_small_all_lengths() { for config in TEST_ALL_CONFIGS { // Test each length from 1 to 255 @@ -1091,7 +1114,10 @@ mod lib { } } + /// Skipping for Miri runs due to time constraints, underlying code already covered by other + /// tests. #[test] + #[cfg_attr(miri, ignore)] fn test_medium_lengths() { for config in TEST_ALL_CONFIGS { // Test each length from 256 to 1024, which should fold and include handling remainders @@ -1101,7 +1127,10 @@ mod lib { } } + /// Skipping for Miri runs due to time constraints, underlying code already covered by other + /// tests. #[test] + #[cfg_attr(miri, ignore)] fn test_large_lengths() { for config in TEST_ALL_CONFIGS { // Test 1 MiB just before, at, and just after the folding boundaries @@ -1198,7 +1227,10 @@ mod lib { ); } + /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already + /// covered by other tests. #[test] + #[cfg_attr(miri, ignore)] fn test_checksum_file() { // Create a test file with repeating zeros let test_file_path = "test/test_crc32_hash_file.bin"; @@ -1216,7 +1248,10 @@ mod lib { std::fs::remove_file(test_file_path).unwrap(); } + /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already + /// covered by other tests. #[test] + #[cfg_attr(miri, ignore)] fn test_checksum_file_with_custom_params() { crate::cache::clear_cache(); @@ -1264,7 +1299,10 @@ mod lib { assert_eq!(result, check); } + /// Skipping for Miri runs due to isolation constraints, underlying code other than I/O already + /// covered by other tests. #[test] + #[cfg_attr(miri, ignore)] fn test_writer() { // Create a test file with repeating zeros let test_file_path = "test/test_crc32_writer_file.bin"; @@ -1359,6 +1397,7 @@ mod lib { /// Tests whether the FFI header is up-to-date #[test] + #[cfg_attr(miri, ignore)] fn test_ffi_header() -> Result<(), String> { #[cfg(target_os = "windows")] { From 6626c6c15dbb3e06b740b34dd936ad4cd6d3ff8f Mon Sep 17 00:00:00 2001 From: Don MacAskill Date: Sat, 8 Nov 2025 17:28:40 -0800 Subject: [PATCH 2/5] Fix typo --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 15d60ba..317fab0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -897,7 +897,7 @@ fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 #[cfg(target_arch = "aarch64")] { use std::arch::is_aarch64_feature_detected; - if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("aes") { + if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") { return fusion::crc32_iso_hdlc(state as u32, data) as u64; } } From bf83741601cf3f5ca5234743b7041512a16c06ec Mon Sep 17 00:00:00 2001 From: Don MacAskill Date: Sat, 8 Nov 2025 17:28:55 -0800 Subject: [PATCH 3/5] Improve cache key Use all features of the CRC definition. --- src/arch/software.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/arch/software.rs b/src/arch/software.rs index 52d9f7b..8a5957d 100644 --- a/src/arch/software.rs +++ b/src/arch/software.rs @@ -80,8 +80,13 @@ const RUST_CRC64_WE: crc::Crc> = crc::Crc::>::new( #[allow(unused)] const RUST_CRC64_XZ: crc::Crc> = crc::Crc::>::new(&crc::CRC_64_XZ); -static CUSTOM_CRC32_CACHE: OnceLock>>> = OnceLock::new(); -static CUSTOM_CRC64_CACHE: OnceLock>>> = OnceLock::new(); +static CUSTOM_CRC32_CACHE: OnceLock>>> = + OnceLock::new(); +static CUSTOM_CRC64_CACHE: OnceLock>>> = + OnceLock::new(); + +type Crc32Key = (u32, u32, bool, bool, u32, u32); +type Crc64Key = (u64, u64, bool, bool, u64, u64); #[allow(unused)] // Dispatch function that handles the generic case @@ -106,7 +111,14 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { let mut cache = cache.lock().unwrap(); // Create a key from params that uniquely identifies this algorithm - let key = params.poly as u32; + let key: Crc32Key = ( + params.poly as u32, + params.init as u32, + params.refin, + params.refout, + params.xorout as u32, + params.check as u32, + ); let static_algorithm = cache.entry(key).or_insert_with(|| { let algorithm = Algorithm { @@ -141,7 +153,14 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { let cache = CUSTOM_CRC64_CACHE.get_or_init(|| Mutex::new(HashMap::new())); let mut cache = cache.lock().unwrap(); - let key = params.poly; + let key: Crc64Key = ( + params.poly, + params.init, + params.refin, + params.refout, + params.xorout, + params.check, + ); let static_algorithm = cache.entry(key).or_insert_with(|| { let algorithm = Algorithm { From fda754e189717dd7d75369dd19b9f35db4fb2b05 Mon Sep 17 00:00:00 2001 From: Don MacAskill Date: Sat, 8 Nov 2025 21:07:41 -0800 Subject: [PATCH 4/5] Exclude a few tests from Miri MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Miri won’t allow Command::new() due to isolation restrictions. --- tests/checksum_integration_tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/checksum_integration_tests.rs b/tests/checksum_integration_tests.rs index 3d7e331..bd45966 100644 --- a/tests/checksum_integration_tests.rs +++ b/tests/checksum_integration_tests.rs @@ -6,6 +6,7 @@ use std::fs; use std::process::Command; #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_flag_parsing() { let output = Command::new("cargo") .args(&[ @@ -33,6 +34,7 @@ fn test_benchmark_flag_parsing() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_with_size_parameter() { let output = Command::new("cargo") .args(&[ @@ -57,6 +59,7 @@ fn test_benchmark_with_size_parameter() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_with_duration_parameter() { let output = Command::new("cargo") .args(&[ @@ -81,6 +84,7 @@ fn test_benchmark_with_duration_parameter() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_invalid_size() { let output = Command::new("cargo") .args(&[ @@ -105,6 +109,7 @@ fn test_benchmark_invalid_size() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_invalid_duration() { let output = Command::new("cargo") .args(&[ @@ -129,6 +134,7 @@ fn test_benchmark_invalid_duration() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_with_file_input() { // Create a temporary test file let test_file = "test_benchmark_file.txt"; @@ -162,6 +168,7 @@ fn test_benchmark_with_file_input() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_with_string_input() { let output = Command::new("cargo") .args(&[ @@ -188,6 +195,7 @@ fn test_benchmark_with_string_input() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_different_algorithms() { let algorithms = ["CRC-32/ISCSI", "CRC-64/NVME"]; @@ -220,6 +228,7 @@ fn test_benchmark_different_algorithms() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_size_without_benchmark_flag() { let output = Command::new("cargo") .args(&[ @@ -243,6 +252,7 @@ fn test_benchmark_size_without_benchmark_flag() { } #[test] +#[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_nonexistent_file() { let output = Command::new("cargo") .args(&[ From a81260d0059d9336d3ea970ec90ad4a818e75134 Mon Sep 17 00:00:00 2001 From: Don MacAskill Date: Sat, 8 Nov 2025 21:20:40 -0800 Subject: [PATCH 5/5] WIP. Add Miri to Tests workflow for GH Actions --- .github/workflows/tests.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 17bcf6a..da73749 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -118,4 +118,32 @@ jobs: - name: Architecture check run: cross run --features cli --bin arch-check --target ${{ matrix.target }} - name: Test - run: cross test --features cli --target ${{ matrix.target }} \ No newline at end of file + run: cross test --features cli --target ${{ matrix.target }} + + miri-test-x86_64: + name: Miri Test x86_64 + needs: test-x86_64 + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 + with: + toolchain: nightly + components: miri + - name: Detect CPU cores + id: cores + run: | + CORES=$(nproc) + echo "count=$CORES" >> $GITHUB_OUTPUT + echo "use_nextest=$([[ $CORES -ge 2 ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT + - name: Install nextest + if: steps.cores.outputs.use_nextest == 'true' + run: cargo install cargo-nextest --locked + - name: Setup Miri + run: cargo miri setup + - name: Run Miri tests (parallel) + if: steps.cores.outputs.use_nextest == 'true' + run: cargo miri nextest run --all-features -j${{ steps.cores.outputs.count }} + - name: Run Miri tests (serial) + if: steps.cores.outputs.use_nextest == 'false' + run: cargo miri test --all-features \ No newline at end of file