#![allow(unsafe_code)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use quamina::automaton::FieldMatcher;
use quamina::automaton::arena::{
ARENA_VALUE_TERMINATOR, NfaBuffers, make_shellstyle_arena_fa, traverse_arena_nfa,
};
struct CountingAlloc;
static ALLOCS: AtomicUsize = AtomicUsize::new(0);
static COUNTING_ENABLED: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if COUNTING_ENABLED.load(Ordering::Relaxed) != 0 {
ALLOCS.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if COUNTING_ENABLED.load(Ordering::Relaxed) != 0 {
ALLOCS.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static GLOBAL: CountingAlloc = CountingAlloc;
fn count_steady_state_allocs(value: &[u8], expect_match: bool) -> usize {
let next_field = Arc::new(FieldMatcher::new());
let (mut arena, start) = make_shellstyle_arena_fa(b"A*", next_field);
arena.precompute_epsilon_closures();
arena.flatten_tables();
let mut bufs = NfaBuffers::with_capacity();
for _ in 0..16 {
traverse_arena_nfa(&arena, start, value, &mut bufs);
assert_eq!(
!bufs.transitions.is_empty(),
expect_match,
"warmup produced an unexpected match result — test setup is broken"
);
}
ALLOCS.store(0, Ordering::Relaxed);
COUNTING_ENABLED.store(1, Ordering::Relaxed);
for _ in 0..1000 {
traverse_arena_nfa(&arena, start, value, &mut bufs);
}
COUNTING_ENABLED.store(0, Ordering::Relaxed);
ALLOCS.load(Ordering::Relaxed)
}
#[test]
#[cfg_attr(miri, ignore)]
fn traverse_arena_nfa_is_alloc_free_in_steady_state() {
let mut matching: Vec<u8> = b"ALICE_IN_WONDERLAND_AAAAAAAAAAAA".to_vec();
matching.push(ARENA_VALUE_TERMINATOR);
let match_allocs = count_steady_state_allocs(&matching, true);
assert_eq!(
match_allocs, 0,
"traverse_arena_nfa allocated {match_allocs} time(s) across 1000 steady-state matches; the inner NFA step is supposed to be allocation-free (upstream Go e33139f equivalent)"
);
let mut no_match: Vec<u8> = b"BOB_NEVER_STARTS_WITH_A".to_vec();
no_match.push(ARENA_VALUE_TERMINATOR);
let no_match_allocs = count_steady_state_allocs(&no_match, false);
assert_eq!(
no_match_allocs, 0,
"traverse_arena_nfa allocated {no_match_allocs} time(s) across 1000 no-match traversals"
);
}