Skip to main content

moonpool_explorer/
context.rs

1//! Thread-local exploration context and RNG hooks.
2//!
3//! Stores per-process exploration state and the function pointers used to
4//! communicate with moonpool-sim's RNG system. The RNG hooks are the entire
5//! coupling surface between this crate and moonpool-sim.
6
7use std::cell::{Cell, RefCell};
8
9use crate::energy::EnergyBudget;
10use crate::shared_stats::{SharedRecipe, SharedStats};
11use crate::split_loop::{AdaptiveConfig, Parallelism};
12
13thread_local! {
14    /// Per-process exploration state.
15    static EXPLORER_CTX: RefCell<ExplorerCtx> = RefCell::new(ExplorerCtx::inactive());
16
17    /// Function pointer to get current RNG call count from moonpool-sim.
18    static RNG_GET_COUNT: Cell<fn() -> u64> = const { Cell::new(|| 0) };
19
20    /// Function pointer to reseed the RNG in moonpool-sim.
21    static RNG_RESEED: Cell<fn(u64)> = const { Cell::new(|_| {}) };
22
23    // Shared-memory pointers (set during init, used by split_loop and assertion_slots)
24
25    /// Pointer to cross-process statistics.
26    pub(crate) static SHARED_STATS: Cell<*mut SharedStats> = const { Cell::new(std::ptr::null_mut()) };
27
28    /// Pointer to shared recipe storage for bug-finding timelines.
29    pub(crate) static SHARED_RECIPE: Cell<*mut SharedRecipe> = const { Cell::new(std::ptr::null_mut()) };
30
31    /// Pointer to cross-process explored coverage map.
32    pub(crate) static EXPLORED_MAP_PTR: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
33
34    /// Pointer to per-child coverage bitmap.
35    pub(crate) static COVERAGE_BITMAP_PTR: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
36
37    /// Pointer to shared assertion slot table (raw bytes, counter-based layout).
38    pub(crate) static ASSERTION_TABLE: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
39
40    /// Pointer to shared energy budget (null when adaptive forking is disabled).
41    pub(crate) static ENERGY_BUDGET_PTR: Cell<*mut EnergyBudget> = const { Cell::new(std::ptr::null_mut()) };
42
43    /// Pointer to shared EachBucket memory (null when not initialized).
44    pub(crate) static EACH_BUCKET_PTR: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
45
46    /// Base pointer for per-process bitmap pool (null until first parallel split).
47    pub(crate) static BITMAP_POOL: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
48
49    /// Number of slots in the bitmap pool.
50    pub(crate) static BITMAP_POOL_SLOTS: Cell<usize> = const { Cell::new(0) };
51}
52
53/// Exploration state for the current process.
54pub struct ExplorerCtx {
55    /// Whether exploration is active.
56    pub active: bool,
57    /// Whether this process is a forked child.
58    pub is_child: bool,
59    /// Current fork depth (0 = root).
60    pub depth: u32,
61    /// Maximum allowed fork depth.
62    pub max_depth: u32,
63    /// Current seed for this timeline.
64    pub current_seed: u64,
65    /// Recipe: sequence of `(rng_call_count, child_seed)` pairs describing
66    /// the fork points that led to this timeline.
67    pub recipe: Vec<(u64, u64)>,
68    /// Number of children to fork at each discovery point.
69    pub timelines_per_split: u32,
70    /// Adaptive forking configuration (None = fixed-count mode).
71    pub adaptive: Option<AdaptiveConfig>,
72    /// Parallelism configuration (None = sequential).
73    pub parallelism: Option<Parallelism>,
74    /// Whether this seed is a warm start (explored map has prior coverage).
75    pub warm_start: bool,
76}
77
78impl ExplorerCtx {
79    /// Create an inactive context (exploration disabled).
80    pub fn inactive() -> Self {
81        Self {
82            active: false,
83            is_child: false,
84            depth: 0,
85            max_depth: 0,
86            current_seed: 0,
87            recipe: Vec::new(),
88            timelines_per_split: 0,
89            adaptive: None,
90            parallelism: None,
91            warm_start: false,
92        }
93    }
94}
95
96/// Set the RNG hooks used to communicate with moonpool-sim.
97///
98/// `get_count` returns the current RNG call count.
99/// `reseed` reseeds the RNG with a new seed and resets the call count.
100///
101/// Must be called before [`crate::init`].
102pub fn set_rng_hooks(get_count: fn() -> u64, reseed: fn(u64)) {
103    RNG_GET_COUNT.with(|c| c.set(get_count));
104    RNG_RESEED.with(|c| c.set(reseed));
105}
106
107/// Get the current RNG call count via the registered hook.
108pub(crate) fn rng_get_count() -> u64 {
109    RNG_GET_COUNT.with(|c| (c.get())())
110}
111
112/// Reseed the RNG via the registered hook.
113pub(crate) fn rng_reseed(seed: u64) {
114    RNG_RESEED.with(|c| (c.get())(seed));
115}
116
117/// Read the exploration context.
118pub(crate) fn with_ctx<R>(f: impl FnOnce(&ExplorerCtx) -> R) -> R {
119    EXPLORER_CTX.with(|ctx| f(&ctx.borrow()))
120}
121
122/// Mutate the exploration context.
123pub(crate) fn with_ctx_mut<R>(f: impl FnOnce(&mut ExplorerCtx) -> R) -> R {
124    EXPLORER_CTX.with(|ctx| f(&mut ctx.borrow_mut()))
125}
126
127/// Check if exploration is active.
128pub fn explorer_is_active() -> bool {
129    with_ctx(|ctx| ctx.active)
130}
131
132/// Check if this process is a forked child.
133pub fn explorer_is_child() -> bool {
134    with_ctx(|ctx| ctx.is_child)
135}
136
137/// Get the raw pointer to the assertion table shared memory.
138///
139/// Returns null if the table is not initialized.
140pub fn get_assertion_table_ptr() -> *mut u8 {
141    ASSERTION_TABLE.with(|c| c.get())
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_default_hooks() {
150        // Default get_count returns 0
151        assert_eq!(rng_get_count(), 0);
152        // Default reseed is a no-op (does not panic)
153        rng_reseed(42);
154    }
155
156    #[test]
157    fn test_set_hooks() {
158        thread_local! {
159            static CALL_COUNT: Cell<u64> = const { Cell::new(0) };
160            static LAST_SEED: Cell<u64> = const { Cell::new(0) };
161        }
162
163        set_rng_hooks(
164            || CALL_COUNT.with(|c| c.get()),
165            |seed| LAST_SEED.with(|s| s.set(seed)),
166        );
167
168        CALL_COUNT.with(|c| c.set(42));
169        assert_eq!(rng_get_count(), 42);
170
171        rng_reseed(123);
172        assert_eq!(LAST_SEED.with(|s| s.get()), 123);
173
174        // Reset to defaults
175        set_rng_hooks(|| 0, |_| {});
176    }
177
178    #[test]
179    fn test_inactive_by_default() {
180        assert!(!explorer_is_active());
181        assert!(!explorer_is_child());
182    }
183}