miniextendr_api/gc_protect/tls.rs
1//! TLS-backed convenience API for GC protection.
2//!
3//! Provides an **optional** convenience layer that maintains a thread-local
4//! stack of scope pointers, allowing `tls::protect(x)` without passing
5//! `&ProtectScope` explicitly.
6//!
7//! **Explicit `&ProtectScope` is recommended for most use cases.** It's simpler,
8//! clearer about lifetimes, and doesn't rely on runtime state. Use this TLS
9//! convenience only when threading scope references through deep call stacks
10//! would be excessively verbose.
11//!
12//! # Example
13//!
14//! ```ignore
15//! use miniextendr_api::gc_protect::tls;
16//!
17//! unsafe fn deep_helper(x: SEXP) -> SEXP {
18//! let y = tls::protect(allocate_something());
19//! combine(x, y.get())
20//! }
21//!
22//! unsafe fn call_body(x: SEXP) -> SEXP {
23//! tls::with_protect_scope(|| {
24//! let x = tls::protect(x);
25//! deep_helper(x.get())
26//! })
27//! }
28//! ```
29use super::ProtectScope;
30use crate::ffi::SEXP;
31use std::cell::RefCell;
32use std::ptr::NonNull;
33
34thread_local! {
35 /// Stack of active protection scopes on this thread.
36 static SCOPE_STACK: RefCell<Vec<NonNull<ProtectScope>>> = const { RefCell::new(Vec::new()) };
37}
38
39/// Execute a closure with a new protection scope as the current TLS scope.
40///
41/// The scope is pushed onto the thread-local stack, the closure runs, and
42/// then the scope is popped and dropped (triggering `UNPROTECT(n)`).
43///
44/// # Safety
45///
46/// Must be called from the R main thread.
47///
48/// # Example
49///
50/// ```ignore
51/// unsafe fn my_call(x: SEXP) -> SEXP {
52/// tls::with_protect_scope(|| {
53/// let x = tls::protect(x);
54/// let y = tls::protect(allocate_something());
55/// combine(x.get(), y.get())
56/// })
57/// }
58/// ```
59/// Guard that pops the TLS scope stack on drop (panic-safe cleanup).
60struct TlsScopeGuard {
61 scope_ptr: NonNull<ProtectScope>,
62}
63
64impl Drop for TlsScopeGuard {
65 fn drop(&mut self) {
66 SCOPE_STACK.with(|stack| {
67 let popped = stack.borrow_mut().pop();
68 debug_assert!(
69 popped == Some(self.scope_ptr),
70 "TLS scope stack corrupted: expected to pop same scope"
71 );
72 });
73 }
74}
75
76/// Execute a closure with a protect scope that is accessible via TLS.
77///
78/// # Safety
79///
80/// Must be called from the R main thread.
81#[inline]
82pub unsafe fn with_protect_scope<F, R>(f: F) -> R
83where
84 F: FnOnce() -> R,
85{
86 // SAFETY: caller guarantees R main thread
87 let scope = unsafe { ProtectScope::new() };
88
89 // Push scope pointer onto TLS stack
90 // SAFETY: scope lives for the duration of this function call
91 let scope_ptr = NonNull::from(&scope);
92 SCOPE_STACK.with(|stack| {
93 stack.borrow_mut().push(scope_ptr);
94 });
95
96 // Guard ensures TLS stack is popped even on panic.
97 // The guard must be dropped BEFORE scope (declared after scope),
98 // so the TLS stack is popped before UNPROTECT runs.
99 let _guard = TlsScopeGuard { scope_ptr };
100
101 // Run the user's closure - if it panics, _guard drops and pops TLS,
102 // then scope drops and calls UNPROTECT(n)
103 f()
104}
105
106/// Protect a value using the current TLS scope.
107///
108/// # Panics
109///
110/// Panics if called outside of a [`with_protect_scope`] block.
111///
112/// # Safety
113///
114/// - Must be called from the R main thread
115/// - `x` must be a valid SEXP
116/// - Must be called within a [`with_protect_scope`] block
117///
118/// # Example
119///
120/// ```ignore
121/// tls::with_protect_scope(|| {
122/// let x = tls::protect(some_sexp);
123/// // use x...
124/// })
125/// ```
126#[inline]
127pub unsafe fn protect(x: SEXP) -> TlsRoot {
128 let scope_ptr = SCOPE_STACK.with(|stack| {
129 stack
130 .borrow()
131 .last()
132 .copied()
133 .expect("tls::protect called outside of with_protect_scope")
134 });
135
136 // SAFETY: scope_ptr is valid because we're inside with_protect_scope
137 let scope: &ProtectScope = unsafe { scope_ptr.as_ref() };
138 let root = unsafe { scope.protect(x) };
139
140 TlsRoot { sexp: root.sexp }
141}
142
143/// Protect a value, returning the raw SEXP.
144///
145/// # Panics
146///
147/// Panics if called outside of a [`with_protect_scope`] block.
148///
149/// # Safety
150///
151/// Same as [`protect`].
152#[inline]
153pub unsafe fn protect_raw(x: SEXP) -> SEXP {
154 let scope_ptr = SCOPE_STACK.with(|stack| {
155 stack
156 .borrow()
157 .last()
158 .copied()
159 .expect("tls::protect_raw called outside of with_protect_scope")
160 });
161
162 let scope: &ProtectScope = unsafe { scope_ptr.as_ref() };
163 unsafe { scope.protect_raw(x) }
164}
165
166/// Check if there is an active TLS scope.
167#[inline]
168pub fn has_active_scope() -> bool {
169 SCOPE_STACK.with(|stack| !stack.borrow().is_empty())
170}
171
172/// Get the current scope's protection count.
173///
174/// Returns `None` if no scope is active.
175#[inline]
176pub fn current_count() -> Option<i32> {
177 SCOPE_STACK.with(|stack| {
178 stack.borrow().last().map(|ptr| {
179 // SAFETY: pointer is valid while in with_protect_scope
180 unsafe { ptr.as_ref() }.count()
181 })
182 })
183}
184
185/// Get the nesting depth of TLS scopes.
186#[inline]
187pub fn scope_depth() -> usize {
188 SCOPE_STACK.with(|stack| stack.borrow().len())
189}
190
191/// A rooted SEXP from TLS protection.
192///
193/// This is similar to [`super::Root`] but without a compile-time lifetime tie to
194/// the scope. The protection is valid as long as the enclosing
195/// [`with_protect_scope`] block hasn't exited.
196///
197/// # Warning
198///
199/// Using a `TlsRoot` after its scope has exited is undefined behavior.
200/// The compile-time lifetime checking of [`super::Root`] is safer; use TLS
201/// convenience only when necessary.
202#[derive(Clone, Copy)]
203pub struct TlsRoot {
204 sexp: SEXP,
205}
206
207impl TlsRoot {
208 /// Get the underlying SEXP.
209 #[inline]
210 pub fn get(&self) -> SEXP {
211 self.sexp
212 }
213
214 /// Consume and return the underlying SEXP.
215 #[inline]
216 pub fn into_raw(self) -> SEXP {
217 self.sexp
218 }
219}
220
221impl std::ops::Deref for TlsRoot {
222 type Target = SEXP;
223
224 #[inline]
225 fn deref(&self) -> &Self::Target {
226 &self.sexp
227 }
228}
229// endregion