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    #[must_use]
81    pub fn inactive() -> Self {
82        Self {
83            active: false,
84            is_child: false,
85            depth: 0,
86            max_depth: 0,
87            current_seed: 0,
88            recipe: Vec::new(),
89            timelines_per_split: 0,
90            adaptive: None,
91            parallelism: None,
92            warm_start: false,
93        }
94    }
95}
96
97/// Set the RNG hooks used to communicate with moonpool-sim.
98///
99/// `get_count` returns the current RNG call count.
100/// `reseed` reseeds the RNG with a new seed and resets the call count.
101///
102/// Must be called before [`crate::init`].
103pub fn set_rng_hooks(get_count: fn() -> u64, reseed: fn(u64)) {
104    RNG_GET_COUNT.with(|c| c.set(get_count));
105    RNG_RESEED.with(|c| c.set(reseed));
106}
107
108/// Get the current RNG call count via the registered hook.
109pub(crate) fn rng_get_count() -> u64 {
110    RNG_GET_COUNT.with(|c| (c.get())())
111}
112
113/// Reseed the RNG via the registered hook.
114pub(crate) fn rng_reseed(seed: u64) {
115    RNG_RESEED.with(|c| (c.get())(seed));
116}
117
118/// Read the exploration context.
119pub(crate) fn with_ctx<R>(f: impl FnOnce(&ExplorerCtx) -> R) -> R {
120    EXPLORER_CTX.with(|ctx| f(&ctx.borrow()))
121}
122
123/// Mutate the exploration context.
124pub(crate) fn with_ctx_mut<R>(f: impl FnOnce(&mut ExplorerCtx) -> R) -> R {
125    EXPLORER_CTX.with(|ctx| f(&mut ctx.borrow_mut()))
126}
127
128/// Check if exploration is active.
129#[must_use]
130pub fn explorer_is_active() -> bool {
131    with_ctx(|ctx| ctx.active)
132}
133
134/// Check if this process is a forked child.
135#[must_use]
136pub fn explorer_is_child() -> bool {
137    with_ctx(|ctx| ctx.is_child)
138}
139
140/// Get the raw pointer to the assertion table shared memory.
141///
142/// Returns null if the table is not initialized.
143#[must_use]
144pub fn assertion_table_ptr() -> *mut u8 {
145    ASSERTION_TABLE.with(std::cell::Cell::get)
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_default_hooks() {
154        // Default get_count returns 0
155        assert_eq!(rng_get_count(), 0);
156        // Default reseed is a no-op (does not panic)
157        rng_reseed(42);
158    }
159
160    #[test]
161    fn test_set_hooks() {
162        thread_local! {
163            static CALL_COUNT: Cell<u64> = const { Cell::new(0) };
164            static LAST_SEED: Cell<u64> = const { Cell::new(0) };
165        }
166
167        set_rng_hooks(
168            || CALL_COUNT.with(std::cell::Cell::get),
169            |seed| LAST_SEED.with(|s| s.set(seed)),
170        );
171
172        CALL_COUNT.with(|c| c.set(42));
173        assert_eq!(rng_get_count(), 42);
174
175        rng_reseed(123);
176        assert_eq!(LAST_SEED.with(std::cell::Cell::get), 123);
177
178        // Reset to defaults
179        set_rng_hooks(|| 0, |_| {});
180    }
181
182    #[test]
183    fn test_inactive_by_default() {
184        assert!(!explorer_is_active());
185        assert!(!explorer_is_child());
186    }
187}