use alloc::{format, string::String, vec::Vec};
use std::sync::{Mutex, MutexGuard};
use crate::CompileTimeGuaranteedError;
static TOKEN_PERMUTATION_MUTEX: Mutex<()> = Mutex::new(());
static OWNER_THREAD: Mutex<Option<std::thread::ThreadId>> = Mutex::new(None);
pub struct TokenTestGuard {
_inner: MutexGuard<'static, ()>,
}
impl Drop for TokenTestGuard {
fn drop(&mut self) {
if let Ok(mut owner) = OWNER_THREAD.lock() {
*owner = None;
}
}
}
pub fn lock_token_testing() -> TokenTestGuard {
let current = std::thread::current().id();
if let Ok(owner) = OWNER_THREAD.lock() {
assert!(
*owner != Some(current),
"lock_token_testing: already held by the current thread"
);
}
let guard = TOKEN_PERMUTATION_MUTEX
.lock()
.unwrap_or_else(|e| e.into_inner());
if let Ok(mut owner) = OWNER_THREAD.lock() {
*owner = Some(current);
}
TokenTestGuard { _inner: guard }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompileTimePolicy {
Warn,
WarnStderr,
Fail,
}
#[derive(Debug, Clone)]
pub struct TokenPermutation {
pub label: String,
pub disabled: Vec<&'static str>,
}
impl core::fmt::Display for TokenPermutation {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.label)
}
}
#[must_use]
#[derive(Debug, Clone)]
pub struct PermutationReport {
pub warnings: Vec<String>,
pub permutations_run: usize,
}
impl core::fmt::Display for PermutationReport {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} permutations run", self.permutations_run)?;
for w in &self.warnings {
write!(f, "\n warning: {w}")?;
}
Ok(())
}
}
struct TokenSlot {
name: &'static str,
disable_flags: &'static str,
descendants: &'static [usize],
disable: fn(bool) -> Result<(), CompileTimeGuaranteedError>,
compiled_with: fn() -> Option<bool>,
check_available: fn() -> bool,
}
#[allow(unused_macros)]
macro_rules! token_slot {
($token:ty, $desc:expr) => {{
fn check_avail() -> bool {
<$token as crate::SimdToken>::summon().is_some()
}
TokenSlot {
name: <$token as crate::SimdToken>::NAME,
disable_flags: <$token as crate::SimdToken>::DISABLE_TARGET_FEATURES,
descendants: $desc,
disable: <$token>::dangerously_disable_token_process_wide,
compiled_with: <$token as crate::SimdToken>::compiled_with,
check_available: check_avail,
}
}};
}
fn build_token_slots() -> Vec<TokenSlot> {
#[allow(unused_mut)]
let mut slots = Vec::new();
#[cfg(target_arch = "x86_64")]
{
slots.push(token_slot!(crate::X64V1Token, &[1, 2, 3, 4, 5, 6, 7]));
slots.push(token_slot!(crate::X64V2Token, &[2, 3, 4, 5, 6, 7]));
slots.push(token_slot!(crate::X64CryptoToken, &[4, 5, 6, 7]));
slots.push(token_slot!(crate::X64V3Token, &[4, 5, 6, 7]));
slots.push(token_slot!(crate::X64V3CryptoToken, &[6]));
slots.push(token_slot!(crate::X64V4Token, &[6, 7]));
slots.push(token_slot!(crate::X64V4xToken, &[]));
slots.push(token_slot!(crate::Avx512Fp16Token, &[]));
}
#[cfg(target_arch = "aarch64")]
{
slots.push(token_slot!(crate::NeonToken, &[1, 2, 3, 4, 5]));
slots.push(token_slot!(crate::NeonAesToken, &[]));
slots.push(token_slot!(crate::NeonSha3Token, &[]));
slots.push(token_slot!(crate::NeonCrcToken, &[]));
slots.push(token_slot!(crate::Arm64V2Token, &[5]));
slots.push(token_slot!(crate::Arm64V3Token, &[]));
}
slots
}
fn valid_disabled_subsets(slots: &[TokenSlot], candidates: &[usize]) -> Vec<Vec<usize>> {
let n = candidates.len();
let mut result = Vec::with_capacity(1 << n);
for mask in 0u32..(1u32 << n) {
let subset: Vec<usize> = (0..n)
.filter(|&i| mask & (1 << i) != 0)
.map(|i| candidates[i])
.collect();
let valid = subset.iter().all(|&idx| {
slots[idx]
.descendants
.iter()
.all(|&desc| !candidates.contains(&desc) || subset.contains(&desc))
});
if valid {
result.push(subset);
}
}
result
}
struct ReenableOnDrop<'a> {
slots: &'a [TokenSlot],
indices: &'a [usize],
}
impl Drop for ReenableOnDrop<'_> {
fn drop(&mut self) {
for &idx in self.indices {
let _ = (self.slots[idx].disable)(false);
}
}
}
pub fn for_each_token_permutation(
policy: CompileTimePolicy,
mut f: impl FnMut(&TokenPermutation),
) -> PermutationReport {
let already_held = OWNER_THREAD
.lock()
.ok()
.is_some_and(|owner| *owner == Some(std::thread::current().id()));
let _guard = if already_held {
None
} else {
let g = TOKEN_PERMUTATION_MUTEX
.lock()
.unwrap_or_else(|e| e.into_inner());
if let Ok(mut owner) = OWNER_THREAD.lock() {
*owner = Some(std::thread::current().id());
}
Some(g)
};
struct ClearOwnerOnDrop(bool);
impl Drop for ClearOwnerOnDrop {
fn drop(&mut self) {
if self.0
&& let Ok(mut owner) = OWNER_THREAD.lock()
{
*owner = None;
}
}
}
let _clear_owner = ClearOwnerOnDrop(!already_held && _guard.is_some());
let slots = build_token_slots();
for slot in &slots {
let _ = (slot.disable)(false);
}
let mut warnings = Vec::new();
let mut candidates = Vec::new();
for (i, slot) in slots.iter().enumerate() {
if !(slot.check_available)() {
continue;
}
if (slot.compiled_with)() == Some(true) {
let msg = format!(
"{}: compile-time guaranteed, excluded from permutations. \
To include it, either:\n\
\x20 1. Add `testable_dispatch` to archmage features in Cargo.toml\n\
\x20 2. Remove `-Ctarget-cpu` from RUSTFLAGS\n\
\x20 3. Compile with RUSTFLAGS=\"{}\"",
slot.name, slot.disable_flags,
);
match policy {
CompileTimePolicy::Warn => {
warnings.push(msg);
}
CompileTimePolicy::WarnStderr => {
eprintln!("warning: {msg}");
warnings.push(msg);
}
CompileTimePolicy::Fail => {
panic!("{msg}");
}
}
continue;
}
candidates.push(i);
}
let subsets = valid_disabled_subsets(&slots, &candidates);
let mut permutations_run = 0;
for subset in &subsets {
let disabled_names: Vec<&'static str> = subset.iter().map(|&i| slots[i].name).collect();
let label = if subset.is_empty() {
String::from("all enabled")
} else {
format!("{} disabled", disabled_names.join(", "))
};
let perm = TokenPermutation {
label,
disabled: disabled_names,
};
for &idx in subset {
let _ = (slots[idx].disable)(true);
}
{
let _reenable = ReenableOnDrop {
slots: &slots,
indices: subset,
};
f(&perm);
}
permutations_run += 1;
}
PermutationReport {
warnings,
permutations_run,
}
}