use super::tier::{
classify_gpu_tier, gpu_min_bytes_for_tier, gpu_pattern_breakeven_for_tier,
gpu_solo_bytes_for_tier,
};
use super::{HardwareCaps, ScanBackend};
thread_local! {
pub(crate) static TEST_BACKEND_OVERRIDE: std::cell::RefCell<Option<Option<ScanBackend>>> = const { std::cell::RefCell::new(None) };
}
pub fn set_test_backend_override(val: Option<ScanBackend>) {
TEST_BACKEND_OVERRIDE.with(|cell| {
*cell.borrow_mut() = Some(val);
});
}
pub fn clear_test_backend_override() {
TEST_BACKEND_OVERRIDE.with(|cell| {
*cell.borrow_mut() = None;
});
}
#[must_use]
pub fn select_backend(
caps: &HardwareCaps,
workload_bytes: u64,
pattern_count: usize,
) -> ScanBackend {
if let Some(forced) = backend_env_override() {
return forced;
}
if crate::gpu::env_no_gpu() {
if caps.hyperscan_available || caps.has_avx512 || caps.has_avx2 || caps.has_neon {
return ScanBackend::SimdCpu;
}
return ScanBackend::CpuFallback;
}
if gpu_could_engage(caps, workload_bytes, pattern_count) {
return ScanBackend::Gpu;
}
if caps.hyperscan_available {
return ScanBackend::SimdCpu;
}
if caps.has_avx512 || caps.has_avx2 || caps.has_neon {
return ScanBackend::SimdCpu;
}
ScanBackend::CpuFallback
}
#[must_use]
pub fn select_backend_for_batch(
caps: &HardwareCaps,
workload_bytes: u64,
pattern_count: usize,
large_chunk_bytes: u64,
) -> ScanBackend {
if let Some(forced) = backend_env_override() {
return forced;
}
if crate::gpu::env_no_gpu() {
if caps.hyperscan_available || caps.has_avx512 || caps.has_avx2 || caps.has_neon {
return ScanBackend::SimdCpu;
}
return ScanBackend::CpuFallback;
}
let large_dominates =
large_chunk_bytes > 0 && large_chunk_bytes.saturating_mul(2) >= workload_bytes;
if large_dominates && gpu_could_engage(caps, workload_bytes, pattern_count) {
return ScanBackend::Gpu;
}
if caps.hyperscan_available {
return ScanBackend::SimdCpu;
}
if caps.has_avx512 || caps.has_avx2 || caps.has_neon {
return ScanBackend::SimdCpu;
}
ScanBackend::CpuFallback
}
#[must_use]
pub fn gpu_could_engage(caps: &HardwareCaps, workload_bytes: u64, pattern_count: usize) -> bool {
if !caps.gpu_available || caps.gpu_is_software {
return false;
}
let tier = classify_gpu_tier(caps.gpu_name.as_deref());
let solo = gpu_solo_bytes_for_tier(tier);
let min = gpu_min_bytes_for_tier(tier);
let pattern_floor = gpu_pattern_breakeven_for_tier(tier);
workload_bytes >= solo || (workload_bytes >= min && pattern_count >= pattern_floor)
}
pub fn forced_backend_from_env() -> Option<ScanBackend> {
backend_env_override()
}
pub fn forced_backend_from_env_uncached() -> Option<ScanBackend> {
parse_backend_env()
}
pub(super) fn backend_env_override() -> Option<ScanBackend> {
let is_test = cfg!(test)
|| std::env::var("CARGO_MANIFEST_DIR").is_ok()
|| std::env::current_exe()
.ok()
.and_then(|p| p.to_str().map(|s| s.contains("/deps/")))
.unwrap_or(false);
if is_test {
parse_backend_env()
} else {
static CACHED: std::sync::OnceLock<Option<ScanBackend>> = std::sync::OnceLock::new();
*CACHED.get_or_init(parse_backend_env)
}
}
pub(super) fn parse_backend_env() -> Option<ScanBackend> {
if let Some(forced) = TEST_BACKEND_OVERRIDE.with(|cell| *cell.borrow()) {
return forced;
}
let raw = std::env::var("KEYHOG_BACKEND").ok()?;
parse_backend_str(&raw)
}
pub fn parse_backend_str(raw: &str) -> Option<ScanBackend> {
match raw.trim().to_ascii_lowercase().as_str() {
"gpu" | "gpu-zero-copy" | "literal-set" => Some(ScanBackend::Gpu),
"mega-scan" | "gpu-mega-scan" | "regex-nfa" | "rule-pipeline" => {
Some(ScanBackend::MegaScan)
}
"simd" | "simd-regex" | "hyperscan" => Some(ScanBackend::SimdCpu),
"cpu" | "cpu-fallback" | "scalar" => Some(ScanBackend::CpuFallback),
_ => None,
}
}