Skip to main content

tcontract_exec/
pool.rs

1use crate::ArenaProvider;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::{Arc, Mutex};
4
5/// A wrapper around a host's borrowed, owned or shared Rayon pool.
6///
7/// tcontract never creates threads beyond this pool. Its size is fixed at
8/// construction (Rayon pools cannot resize). The embedded mutex serializes
9/// SPMD broadcasts through this wrapper. Hosts sharing a raw pool must retain
10/// one wrapper and serialize potentially conflicting numerical work, rather
11/// than constructing independent wrappers with separate SPMD mutexes.
12///
13/// # Examples
14///
15/// ```
16/// let tp = rayon::ThreadPoolBuilder::new().num_threads(3).build().unwrap();
17/// let pool = tcontract_exec::Pool::borrow(&tp);
18/// assert_eq!(pool.size(), 3);
19/// assert_eq!(pool.stats().entries, 0);
20/// ```
21pub struct Pool<'p> {
22    pool: PoolRef<'p>,
23    pub(crate) spmd: Mutex<()>,
24    /// Retained storage for operations through this wrapper. Reusing the wrapper
25    /// reuses its worker buffers, B panel and barriered team sets.
26    workspace: ArenaProvider,
27    entries: AtomicU64,
28    broadcasts: AtomicU64,
29    inline_runs: AtomicU64,
30}
31
32enum PoolRef<'p> {
33    Borrowed(&'p rayon::ThreadPool),
34    Owned(Box<rayon::ThreadPool>),
35    Shared(Arc<rayon::ThreadPool>),
36}
37
38/// Counters of how often a [`Pool`] was entered, for tests and benchmarks.
39///
40/// # Examples
41///
42/// ```
43/// assert_eq!(tcontract_exec::PoolStats::default().entries, 0);
44/// ```
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46pub struct PoolStats {
47    /// Entries from outside the pool (`install` or partitions).
48    pub entries: u64,
49    /// SPMD broadcasts started from outside the pool.
50    pub broadcasts: u64,
51    /// Parallel requests served in place because the caller was a worker.
52    pub inline_runs: u64,
53}
54
55impl<'p> Pool<'p> {
56    /// Borrow a host pool.
57    pub fn borrow(pool: &'p rayon::ThreadPool) -> Self {
58        Self::with(PoolRef::Borrowed(pool))
59    }
60
61    fn with(pool: PoolRef<'p>) -> Self {
62        Self {
63            pool,
64            spmd: Mutex::new(()),
65            workspace: ArenaProvider::new(),
66            entries: AtomicU64::new(0),
67            broadcasts: AtomicU64::new(0),
68            inline_runs: AtomicU64::new(0),
69        }
70    }
71
72    /// The storage this pool lends to every operation that runs on it.
73    ///
74    /// Borrowed, not copied: one pool is one owner, and a driver that is given
75    /// this provider returns its team sets to it on drop.
76    pub fn workspace(&self) -> &ArenaProvider {
77        &self.workspace
78    }
79
80    /// Release the pool's idle workspace storage. Live leases and borrowed
81    /// worker buffers survive; a worker parked on this pool keeps its slot
82    /// until it is idle again.
83    pub fn trim_workspace(&self) {
84        use crate::WorkspaceProvider;
85        self.workspace.trim();
86    }
87
88    /// Number of workers.
89    pub fn size(&self) -> usize {
90        self.tp().current_num_threads()
91    }
92
93    pub(crate) fn tp(&self) -> &rayon::ThreadPool {
94        match &self.pool {
95            PoolRef::Borrowed(p) => p,
96            PoolRef::Owned(p) => p,
97            PoolRef::Shared(p) => p,
98        }
99    }
100
101    /// Give back an owned pool (`None` for a borrowed or shared one), for example
102    /// so a C host can drop it and join its workers. A shared wrapper releases
103    /// only its Arc reference; other owners keep the raw pool alive.
104    pub fn into_owned(self) -> Option<rayon::ThreadPool> {
105        match self.pool {
106            PoolRef::Owned(p) => Some(*p),
107            PoolRef::Borrowed(_) | PoolRef::Shared(_) => None,
108        }
109    }
110
111    /// Snapshot of the entry counters.
112    pub fn stats(&self) -> PoolStats {
113        PoolStats {
114            entries: self.entries.load(Ordering::Relaxed),
115            broadcasts: self.broadcasts.load(Ordering::Relaxed),
116            inline_runs: self.inline_runs.load(Ordering::Relaxed),
117        }
118    }
119
120    /// Zero the counters.
121    pub fn reset_stats(&self) {
122        self.entries.store(0, Ordering::Relaxed);
123        self.broadcasts.store(0, Ordering::Relaxed);
124        self.inline_runs.store(0, Ordering::Relaxed);
125    }
126
127    pub(crate) fn is_worker(&self) -> bool {
128        self.tp().current_thread_index().is_some()
129    }
130
131    pub(crate) fn count_entry(&self) {
132        self.entries.fetch_add(1, Ordering::Relaxed);
133    }
134
135    pub(crate) fn count_broadcast(&self) {
136        self.broadcasts.fetch_add(1, Ordering::Relaxed);
137    }
138
139    pub(crate) fn count_inline(&self) {
140        self.inline_runs.fetch_add(1, Ordering::Relaxed);
141    }
142}
143
144impl Pool<'static> {
145    /// Share the host's existing Rayon pool without creating another worker team.
146    ///
147    /// Retain this wrapper across calls and share it (for example through an
148    /// `Arc<Pool<'static>>`) across host clones to reuse workspace and SPMD
149    /// serialization. Other numerical libraries may borrow the host's original
150    /// raw pool, but their work must obey the host's same resource-loan protocol.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use std::sync::Arc;
156    /// let raw = Arc::new(rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap());
157    /// let pool = tcontract_exec::Pool::shared(Arc::clone(&raw));
158    /// assert_eq!(pool.size(), raw.current_num_threads());
159    /// ```
160    pub fn shared(pool: Arc<rayon::ThreadPool>) -> Self {
161        Self::with(PoolRef::Shared(pool))
162    }
163
164    /// Take ownership of a pool, for hosts without one of their own (the C
165    /// ABI's `tcontract_tapp_executor_create_rayon`). Rust hosts normally [`Pool::borrow`].
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// let tp = rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap();
171    /// let pool = tcontract_exec::Pool::owned(tp);
172    /// assert_eq!(pool.size(), 2);
173    /// ```
174    pub fn owned(pool: rayon::ThreadPool) -> Self {
175        Self::with(PoolRef::Owned(Box::new(pool)))
176    }
177}
178
179impl std::fmt::Debug for Pool<'_> {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        f.debug_struct("Pool")
182            .field("size", &self.size())
183            .field("stats", &self.stats())
184            .finish()
185    }
186}