Skip to main content

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