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 energy budget (null when adaptive forking is disabled).
38    pub(crate) static ENERGY_BUDGET_PTR: Cell<*mut EnergyBudget> = const { Cell::new(std::ptr::null_mut()) };
39
40    /// Base pointer for per-process bitmap pool (null until first parallel split).
41    pub(crate) static BITMAP_POOL: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
42
43    /// Number of slots in the bitmap pool.
44    pub(crate) static BITMAP_POOL_SLOTS: Cell<usize> = const { Cell::new(0) };
45}
46
47/// Exploration state for the current process.
48pub struct ExplorerCtx {
49    /// Whether exploration is active.
50    pub active: bool,
51    /// Whether this process is a forked child.
52    pub is_child: bool,
53    /// Current fork depth (0 = root).
54    pub depth: u32,
55    /// Maximum allowed fork depth.
56    pub max_depth: u32,
57    /// Current seed for this timeline.
58    pub current_seed: u64,
59    /// Recipe: sequence of `(rng_call_count, child_seed)` pairs describing
60    /// the fork points that led to this timeline.
61    pub recipe: Vec<(u64, u64)>,
62    /// Number of children to fork at each discovery point.
63    pub timelines_per_split: u32,
64    /// Adaptive forking configuration (None = fixed-count mode).
65    pub adaptive: Option<AdaptiveConfig>,
66    /// Parallelism configuration (None = sequential).
67    pub parallelism: Option<Parallelism>,
68    /// Whether this seed is a warm start (explored map has prior coverage).
69    pub warm_start: bool,
70}
71
72impl ExplorerCtx {
73    /// Create an inactive context (exploration disabled).
74    #[must_use]
75    pub fn inactive() -> Self {
76        Self {
77            active: false,
78            is_child: false,
79            depth: 0,
80            max_depth: 0,
81            current_seed: 0,
82            recipe: Vec::new(),
83            timelines_per_split: 0,
84            adaptive: None,
85            parallelism: None,
86            warm_start: false,
87        }
88    }
89}
90
91/// Set the RNG hooks used to communicate with moonpool-sim.
92///
93/// `get_count` returns the current RNG call count.
94/// `reseed` reseeds the RNG with a new seed and resets the call count.
95///
96/// Must be called before [`crate::init`].
97pub fn set_rng_hooks(get_count: fn() -> u64, reseed: fn(u64)) {
98    RNG_GET_COUNT.with(|c| c.set(get_count));
99    RNG_RESEED.with(|c| c.set(reseed));
100}
101
102/// Get the current RNG call count via the registered hook.
103pub(crate) fn rng_get_count() -> u64 {
104    RNG_GET_COUNT.with(|c| (c.get())())
105}
106
107/// Reseed the RNG via the registered hook.
108pub(crate) fn rng_reseed(seed: u64) {
109    RNG_RESEED.with(|c| (c.get())(seed));
110}
111
112/// Read the exploration context.
113pub(crate) fn with_ctx<R>(f: impl FnOnce(&ExplorerCtx) -> R) -> R {
114    EXPLORER_CTX.with(|ctx| f(&ctx.borrow()))
115}
116
117/// Mutate the exploration context.
118pub(crate) fn with_ctx_mut<R>(f: impl FnOnce(&mut ExplorerCtx) -> R) -> R {
119    EXPLORER_CTX.with(|ctx| f(&mut ctx.borrow_mut()))
120}
121
122/// Check if exploration is active.
123#[must_use]
124pub fn explorer_is_active() -> bool {
125    with_ctx(|ctx| ctx.active)
126}
127
128/// Check if this process is a forked child.
129#[must_use]
130pub fn explorer_is_child() -> bool {
131    with_ctx(|ctx| ctx.is_child)
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_default_hooks() {
140        // Default get_count returns 0
141        assert_eq!(rng_get_count(), 0);
142        // Default reseed is a no-op (does not panic)
143        rng_reseed(42);
144    }
145
146    #[test]
147    fn test_set_hooks() {
148        thread_local! {
149            static CALL_COUNT: Cell<u64> = const { Cell::new(0) };
150            static LAST_SEED: Cell<u64> = const { Cell::new(0) };
151        }
152
153        set_rng_hooks(
154            || CALL_COUNT.with(std::cell::Cell::get),
155            |seed| LAST_SEED.with(|s| s.set(seed)),
156        );
157
158        CALL_COUNT.with(|c| c.set(42));
159        assert_eq!(rng_get_count(), 42);
160
161        rng_reseed(123);
162        assert_eq!(LAST_SEED.with(std::cell::Cell::get), 123);
163
164        // Reset to defaults
165        set_rng_hooks(|| 0, |_| {});
166    }
167
168    #[test]
169    fn test_inactive_by_default() {
170        assert!(!explorer_is_active());
171        assert!(!explorer_is_child());
172    }
173}