use rustc_hash::FxHashMap;
#[cfg(target_feature = "avx512f")]
const VECTOR_WIDTH: usize = 16; #[cfg(all(target_feature = "avx2", not(target_feature = "avx512f")))]
const VECTOR_WIDTH: usize = 8; #[cfg(all(target_feature = "sse4.2", not(target_feature = "avx2")))]
const VECTOR_WIDTH: usize = 4; #[cfg(not(any(
target_feature = "sse4.2",
target_feature = "avx2",
target_feature = "avx512f"
)))]
const VECTOR_WIDTH: usize = 1;
pub struct SimdActivityCounter {
counts: Vec<u32>,
}
impl SimdActivityCounter {
pub fn new(num_activities: usize) -> Self {
SimdActivityCounter {
counts: vec![
0u32;
(num_activities + VECTOR_WIDTH - 1) & !VECTOR_WIDTH.saturating_sub(1)
],
}
}
#[inline]
pub fn increment_batch(&mut self, activity_ids: &[u32]) {
if activity_ids.is_empty() {
return;
}
#[cfg(target_feature = "avx512f")]
{
self.increment_batch_avx512(activity_ids);
return;
}
#[cfg(all(target_feature = "avx2", not(target_feature = "avx512f")))]
{
self.increment_batch_avx2(activity_ids);
return;
}
#[cfg(all(target_feature = "sse4.2", not(target_feature = "avx2")))]
{
self.increment_batch_sse4_2(activity_ids);
return;
}
self.increment_batch_scalar(activity_ids);
}
#[cfg(target_feature = "avx512f")]
#[inline]
fn increment_batch_avx512(&mut self, activity_ids: &[u32]) {
unsafe {
let ptr = self.counts.as_mut_ptr() as *mut u32;
for &id in activity_ids {
let idx = id as usize;
if idx < self.counts.len() {
let current = std::ptr::read(ptr.add(idx));
std::ptr::write(ptr.add(idx), current.wrapping_add(1));
}
}
}
}
#[cfg(all(target_feature = "avx2", not(target_feature = "avx512f")))]
#[inline]
fn increment_batch_avx2(&mut self, activity_ids: &[u32]) {
let mut i = 0;
let len = activity_ids.len();
while i + 8 <= len {
unsafe {
let ids = [
activity_ids[i],
activity_ids[i + 1],
activity_ids[i + 2],
activity_ids[i + 3],
activity_ids[i + 4],
activity_ids[i + 5],
activity_ids[i + 6],
activity_ids[i + 7],
];
let ptr = self.counts.as_mut_ptr() as *mut u32;
for &id in &ids {
let idx = id as usize;
if idx < self.counts.len() {
let current = std::ptr::read(ptr.add(idx));
std::ptr::write(ptr.add(idx), current.wrapping_add(1));
}
}
}
i += 8;
}
while i < len {
let idx = activity_ids[i] as usize;
if idx < self.counts.len() {
self.counts[idx] = self.counts[idx].wrapping_add(1);
}
i += 1;
}
}
#[cfg(all(target_feature = "sse4.2", not(target_feature = "avx2")))]
#[inline]
fn increment_batch_sse4_2(&mut self, activity_ids: &[u32]) {
let mut i = 0;
let len = activity_ids.len();
while i + 4 <= len {
unsafe {
let ptr = self.counts.as_mut_ptr() as *mut u32;
for j in 0..4 {
let id = activity_ids[i + j];
let idx = id as usize;
if idx < self.counts.len() {
let current = std::ptr::read(ptr.add(idx));
std::ptr::write(ptr.add(idx), current.wrapping_add(1));
}
}
}
i += 4;
}
while i < len {
let idx = activity_ids[i] as usize;
if idx < self.counts.len() {
self.counts[idx] = self.counts[idx].wrapping_add(1);
}
i += 1;
}
}
#[inline]
fn increment_batch_scalar(&mut self, activity_ids: &[u32]) {
let mut i = 0;
let len = activity_ids.len();
while i + 4 <= len {
self.counts[activity_ids[i] as usize] =
self.counts[activity_ids[i] as usize].wrapping_add(1);
self.counts[activity_ids[i + 1] as usize] =
self.counts[activity_ids[i + 1] as usize].wrapping_add(1);
self.counts[activity_ids[i + 2] as usize] =
self.counts[activity_ids[i + 2] as usize].wrapping_add(1);
self.counts[activity_ids[i + 3] as usize] =
self.counts[activity_ids[i + 3] as usize].wrapping_add(1);
i += 4;
}
while i < len {
self.counts[activity_ids[i] as usize] =
self.counts[activity_ids[i] as usize].wrapping_add(1);
i += 1;
}
}
pub fn counts(&self) -> &[u32] {
&self.counts
}
pub fn reset(&mut self) {
self.counts.iter_mut().for_each(|c| *c = 0);
}
}
pub struct SimdEdgeAggregator {
edges: FxHashMap<(u32, u32), u64>,
}
impl Default for SimdEdgeAggregator {
fn default() -> Self {
Self::new()
}
}
impl SimdEdgeAggregator {
pub fn new() -> Self {
SimdEdgeAggregator {
edges: FxHashMap::default(),
}
}
#[inline]
pub fn increment_batch(&mut self, edge_pairs: &[(u32, u32)]) {
if edge_pairs.is_empty() {
return;
}
let mut i = 0;
let len = edge_pairs.len();
while i + 4 <= len {
*self.edges.entry(edge_pairs[i]).or_insert(0) += 1;
*self.edges.entry(edge_pairs[i + 1]).or_insert(0) += 1;
*self.edges.entry(edge_pairs[i + 2]).or_insert(0) += 1;
*self.edges.entry(edge_pairs[i + 3]).or_insert(0) += 1;
i += 4;
}
while i < len {
*self.edges.entry(edge_pairs[i]).or_insert(0) += 1;
i += 1;
}
}
pub fn edges(&self) -> &FxHashMap<(u32, u32), u64> {
&self.edges
}
pub fn reset(&mut self) {
self.edges.clear();
}
}
pub struct SimdMarkingUpdater {
marking: Vec<u32>,
}
impl SimdMarkingUpdater {
pub fn new(num_places: usize) -> Self {
SimdMarkingUpdater {
marking: vec![0u32; num_places],
}
}
#[inline]
pub fn fire_transition(&mut self, preset: &[u32], postset: &[u32]) -> (u32, u32, bool) {
let mut success = true;
for &place_id in preset {
if place_id as usize >= self.marking.len() || self.marking[place_id as usize] == 0 {
success = false;
break;
}
}
if !success {
return (0, 0, false);
}
let consumed = preset.len() as u32;
#[cfg(target_feature = "avx2")]
{
self.consume_batch_avx2(preset);
}
#[cfg(all(not(target_feature = "avx2"), target_feature = "sse4.2"))]
{
self.consume_batch_sse4_2(preset);
}
#[cfg(not(any(target_feature = "avx2", target_feature = "sse4.2")))]
{
self.consume_batch_scalar(preset);
}
let produced = postset.len() as u32;
#[cfg(target_feature = "avx2")]
{
self.produce_batch_avx2(postset);
}
#[cfg(all(not(target_feature = "avx2"), target_feature = "sse4.2"))]
{
self.produce_batch_sse4_2(postset);
}
#[cfg(not(any(target_feature = "avx2", target_feature = "sse4.2")))]
{
self.produce_batch_scalar(postset);
}
(consumed, produced, true)
}
#[cfg(target_feature = "avx2")]
#[inline]
fn consume_batch_avx2(&mut self, places: &[u32]) {
let mut i = 0;
let len = places.len();
while i + 8 <= len {
for j in 0..8 {
let place_id = places[i + j] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].saturating_sub(1);
}
}
i += 8;
}
while i < len {
let place_id = places[i] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].saturating_sub(1);
}
i += 1;
}
}
#[cfg(all(not(target_feature = "avx2"), target_feature = "sse4.2"))]
#[inline]
fn consume_batch_sse4_2(&mut self, places: &[u32]) {
let mut i = 0;
let len = places.len();
while i + 4 <= len {
for j in 0..4 {
let place_id = places[i + j] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].saturating_sub(1);
}
}
i += 4;
}
while i < len {
let place_id = places[i] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].saturating_sub(1);
}
i += 1;
}
}
#[cfg(not(any(target_feature = "avx2", target_feature = "sse4.2")))]
#[inline]
fn consume_batch_scalar(&mut self, places: &[u32]) {
let mut i = 0;
let len = places.len();
while i + 4 <= len {
self.marking[places[i] as usize] = self.marking[places[i] as usize].saturating_sub(1);
self.marking[places[i + 1] as usize] =
self.marking[places[i + 1] as usize].saturating_sub(1);
self.marking[places[i + 2] as usize] =
self.marking[places[i + 2] as usize].saturating_sub(1);
self.marking[places[i + 3] as usize] =
self.marking[places[i + 3] as usize].saturating_sub(1);
i += 4;
}
while i < len {
self.marking[places[i] as usize] = self.marking[places[i] as usize].saturating_sub(1);
i += 1;
}
}
#[cfg(target_feature = "avx2")]
#[inline]
fn produce_batch_avx2(&mut self, places: &[u32]) {
let mut i = 0;
let len = places.len();
while i + 8 <= len {
for j in 0..8 {
let place_id = places[i + j] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].wrapping_add(1);
}
}
i += 8;
}
while i < len {
let place_id = places[i] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].wrapping_add(1);
}
i += 1;
}
}
#[cfg(all(not(target_feature = "avx2"), target_feature = "sse4.2"))]
#[inline]
fn produce_batch_sse4_2(&mut self, places: &[u32]) {
let mut i = 0;
let len = places.len();
while i + 4 <= len {
for j in 0..4 {
let place_id = places[i + j] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].wrapping_add(1);
}
}
i += 4;
}
while i < len {
let place_id = places[i] as usize;
if place_id < self.marking.len() {
self.marking[place_id] = self.marking[place_id].wrapping_add(1);
}
i += 1;
}
}
#[cfg(not(any(target_feature = "avx2", target_feature = "sse4.2")))]
#[inline]
fn produce_batch_scalar(&mut self, places: &[u32]) {
let mut i = 0;
let len = places.len();
while i + 4 <= len {
self.marking[places[i] as usize] = self.marking[places[i] as usize].wrapping_add(1);
self.marking[places[i + 1] as usize] =
self.marking[places[i + 1] as usize].wrapping_add(1);
self.marking[places[i + 2] as usize] =
self.marking[places[i + 2] as usize].wrapping_add(1);
self.marking[places[i + 3] as usize] =
self.marking[places[i + 3] as usize].wrapping_add(1);
i += 4;
}
while i < len {
self.marking[places[i] as usize] = self.marking[places[i] as usize].wrapping_add(1);
i += 1;
}
}
pub fn marking(&self) -> &[u32] {
&self.marking
}
pub fn reset(&mut self) {
self.marking.iter_mut().for_each(|m| *m = 0);
}
pub fn set(&mut self, place_id: usize, tokens: u32) {
if place_id < self.marking.len() {
self.marking[place_id] = tokens;
}
}
}
pub struct SimdVariantDeduplicator {
variant_hashes: FxHashMap<u64, usize>, scratch: Vec<u64>,
}
impl Default for SimdVariantDeduplicator {
fn default() -> Self {
Self::new()
}
}
impl SimdVariantDeduplicator {
pub fn new() -> Self {
SimdVariantDeduplicator {
variant_hashes: FxHashMap::default(),
scratch: Vec::new(),
}
}
pub fn add_variant(&mut self, trace: &[u32]) -> usize {
let hash = self.compute_variant_hash(trace);
let count = self.variant_hashes.entry(hash).or_insert(0);
*count += 1;
*count
}
#[inline]
fn compute_variant_hash(&mut self, trace: &[u32]) -> u64 {
if trace.is_empty() {
return 14695981039346656037u64; }
let mut hash = 14695981039346656037u64;
let mut i = 0;
let len = trace.len();
while i + 8 <= len {
hash = fnv1a_hash(hash, trace[i]);
hash = fnv1a_hash(hash, trace[i + 1]);
hash = fnv1a_hash(hash, trace[i + 2]);
hash = fnv1a_hash(hash, trace[i + 3]);
hash = fnv1a_hash(hash, trace[i + 4]);
hash = fnv1a_hash(hash, trace[i + 5]);
hash = fnv1a_hash(hash, trace[i + 6]);
hash = fnv1a_hash(hash, trace[i + 7]);
i += 8;
}
while i < len {
hash = fnv1a_hash(hash, trace[i]);
i += 1;
}
hash
}
pub fn variants(&self) -> &FxHashMap<u64, usize> {
&self.variant_hashes
}
pub fn reset(&mut self) {
self.variant_hashes.clear();
self.scratch.clear();
}
}
#[inline(always)]
fn fnv1a_hash(mut hash: u64, byte: u32) -> u64 {
const FNV_PRIME: u64 = 1099511628211u64;
hash ^= byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
hash
}
pub struct SimdTokenAccumulator {
produced: u64,
consumed: u64,
missing: u64,
remaining: u64,
}
impl Default for SimdTokenAccumulator {
fn default() -> Self {
Self::new()
}
}
impl SimdTokenAccumulator {
pub fn new() -> Self {
SimdTokenAccumulator {
produced: 0,
consumed: 0,
missing: 0,
remaining: 0,
}
}
#[inline(always)]
pub fn add_produced(&mut self, count: u64) {
self.produced = self.produced.wrapping_add(count);
}
#[inline(always)]
pub fn add_consumed(&mut self, count: u64) {
self.consumed = self.consumed.wrapping_add(count);
}
#[inline(always)]
pub fn add_missing(&mut self, count: u64) {
self.missing = self.missing.wrapping_add(count);
}
#[inline(always)]
pub fn add_remaining(&mut self, count: u64) {
self.remaining = self.remaining.wrapping_add(count);
}
pub fn totals(&self) -> (u64, u64, u64, u64) {
(self.produced, self.consumed, self.missing, self.remaining)
}
pub fn reset(&mut self) {
self.produced = 0;
self.consumed = 0;
self.missing = 0;
self.remaining = 0;
}
pub fn fitness(&self) -> f64 {
let denom = self.produced as f64 + self.remaining as f64;
if denom == 0.0 {
1.0
} else {
let numer = self.missing as f64 + self.consumed as f64;
(1.0 - (numer / denom)).clamp(0.0, 1.0)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_activity_counter_determinism() {
let mut counter1 = SimdActivityCounter::new(10);
let mut counter2 = SimdActivityCounter::new(10);
let activities = vec![0, 1, 2, 0, 1, 2];
counter1.increment_batch(&activities);
counter2.increment_batch(&[0, 1, 2, 0, 1, 2]);
assert_eq!(counter1.counts(), counter2.counts());
}
#[test]
fn test_activity_counter_accuracy() {
let mut counter = SimdActivityCounter::new(5);
let activities = vec![0, 1, 2, 0, 1, 2, 3, 4, 0];
counter.increment_batch(&activities);
assert_eq!(counter.counts()[0], 3);
assert_eq!(counter.counts()[1], 2);
assert_eq!(counter.counts()[2], 2);
assert_eq!(counter.counts()[3], 1);
assert_eq!(counter.counts()[4], 1);
}
#[test]
fn test_edge_aggregator_accuracy() {
let mut agg = SimdEdgeAggregator::new();
let edges = vec![(0, 1), (1, 2), (0, 1), (2, 3)];
agg.increment_batch(&edges);
assert_eq!(agg.edges().get(&(0, 1)), Some(&2));
assert_eq!(agg.edges().get(&(1, 2)), Some(&1));
assert_eq!(agg.edges().get(&(2, 3)), Some(&1));
}
#[test]
fn test_marking_updater_fire() {
let mut updater = SimdMarkingUpdater::new(5);
updater.set(0, 1);
let (consumed, produced, success) = updater.fire_transition(&[0], &[1, 2]);
assert!(success);
assert_eq!(consumed, 1);
assert_eq!(produced, 2);
assert_eq!(updater.marking()[0], 0);
assert_eq!(updater.marking()[1], 1);
assert_eq!(updater.marking()[2], 1);
}
#[test]
fn test_variant_hash_determinism() {
let mut dedup1 = SimdVariantDeduplicator::new();
let mut dedup2 = SimdVariantDeduplicator::new();
let trace = vec![0, 1, 2, 3, 4, 5, 6, 7];
let count1 = dedup1.add_variant(&trace);
let count2 = dedup2.add_variant(&trace);
assert_eq!(count1, count2);
assert_eq!(dedup1.variants().len(), dedup2.variants().len());
}
#[test]
fn test_token_accumulator_fitness() {
let mut acc = SimdTokenAccumulator::new();
acc.add_produced(100);
acc.add_consumed(10);
acc.add_missing(5);
acc.add_remaining(15);
let fitness = acc.fitness();
assert!((fitness - 0.869565).abs() < 0.001);
}
}