use vyre_foundation::ir::{Program, UnOp};
use crate::bitset::unary_word::bitset_unary_word_program;
pub const OP_ID: &str = "vyre-primitives::bitset::not";
#[must_use]
pub fn bitset_not(input: &str, out: &str, words: u32) -> Program {
bitset_unary_word_program(OP_ID, input, out, words, UnOp::BitNot)
}
#[must_use]
#[cfg(any(test, feature = "cpu-parity"))]
pub fn cpu_ref(input: &[u32]) -> Vec<u32> {
let mut out = Vec::new();
match try_cpu_ref_into(input, &mut out) {
Ok(()) => out,
Err(error) => {
eprintln!("vyre-primitives bitset_not cpu_ref failed: {error}");
Vec::new()
}
}
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn cpu_ref_into(input: &[u32], out: &mut Vec<u32>) {
if let Err(error) = try_cpu_ref_into(input, out) {
eprintln!("vyre-primitives bitset_not cpu_ref_into failed: {error}");
out.clear();
}
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn try_cpu_ref_into(input: &[u32], out: &mut Vec<u32>) -> Result<(), String> {
if input.len() > out.capacity() {
out.try_reserve(input.len() - out.len()).map_err(|err| {
format!(
"bitset_not CPU oracle failed to reserve {} output words: {err}. Fix: shard the bitset before parity evaluation.",
input.len()
)
})?;
}
out.clear();
out.extend(input.iter().map(|word| !word));
Ok(())
}
#[cfg(feature = "inventory-registry")]
inventory::submit! {
crate::harness::OpEntry::new(
OP_ID,
|| bitset_not("input", "out", 1),
Some(|| {
let to_bytes = |w: &[u32]| crate::wire::pack_u32_slice(w);
vec![vec![to_bytes(&[0x0F0F_0F0F]), to_bytes(&[0])]]
}),
Some(|| {
let to_bytes = |w: &[u32]| crate::wire::pack_u32_slice(w);
vec![vec![to_bytes(&[0xF0F0_F0F0])]]
}),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flips_every_bit() {
assert_eq!(cpu_ref(&[0x0F0F_0F0F]), vec![0xF0F0_F0F0]);
}
#[test]
fn empty_bitset() {
assert_eq!(cpu_ref(&[]), Vec::<u32>::new());
}
#[test]
fn single_word_all_bits() {
assert_eq!(cpu_ref(&[0xFFFF_FFFF]), vec![0x0000_0000]);
assert_eq!(cpu_ref(&[0x0000_0000]), vec![0xFFFF_FFFF]);
}
#[test]
fn cross_word_boundary() {
let input = vec![0x8000_0000, 0x0000_0001];
assert_eq!(cpu_ref(&input), vec![0x7FFF_FFFF, 0xFFFF_FFFE]);
}
#[test]
fn cpu_ref_into_truncates_stale_tail_without_reallocating() {
let input = vec![0x8000_0000, 0x0000_0001];
let mut out = Vec::with_capacity(8);
out.extend([0xDEAD_BEEF; 8]);
let ptr = out.as_ptr();
try_cpu_ref_into(&input, &mut out).unwrap();
assert_eq!(out, vec![0x7FFF_FFFF, 0xFFFF_FFFE]);
assert_eq!(out.as_ptr(), ptr);
}
#[test]
fn compatibility_wrappers_match_fallible_reference() {
let input = [0x1234_5678, 0xFFFF_0000];
let mut compat = Vec::with_capacity(4);
let mut fallible = Vec::with_capacity(4);
cpu_ref_into(&input, &mut compat);
try_cpu_ref_into(&input, &mut fallible)
.expect("Fix: small bitset_not CPU oracle must reserve");
assert_eq!(cpu_ref(&input), fallible);
assert_eq!(compat, fallible);
}
#[test]
fn production_cpu_ref_wrappers_have_no_raw_panic_path() {
let production = include_str!("not.rs")
.split("#[cfg(test)]")
.next()
.expect("Fix: not.rs must contain production section");
assert!(
!production.contains(".expect(") && !production.contains(".unwrap("),
"Fix: bitset_not CPU parity wrappers must not panic in production."
);
}
#[test]
fn generated_not_is_involutive_and_matches_scalar_reference() {
for len in 0..96usize {
let input: Vec<u32> = (0..len)
.map(|idx| {
(idx as u32)
.wrapping_mul(0x9E37_79B9)
.wrapping_add(len as u32)
})
.collect();
let mut out = Vec::with_capacity(len + 3);
let mut roundtrip = Vec::with_capacity(len + 3);
try_cpu_ref_into(&input, &mut out).unwrap();
try_cpu_ref_into(&out, &mut roundtrip).unwrap();
assert_eq!(
out,
input.iter().map(|word| !word).collect::<Vec<_>>(),
"generated not case len={len}"
);
assert_eq!(roundtrip, input, "generated involution case len={len}");
}
}
}