1//! Reference-counted GC protection using a `BTreeMap` + VECSXP backing.
2//!
3//! This module provides an alternative to [`gc_protect`](crate::gc_protect) that uses
4//! reference counting instead of R's LIFO protect stack. This allows releasing
5//! protections in any order and avoids the `--max-ppsize` limit.
6//!
7//! # Architecture
8//!
9//! ```text
10//! ┌─────────────────────────────────────────────────────────────────────┐
11//! │ RefCountedArena / ThreadLocalArena │
12//! │ ┌─────────────────────────┐ ┌───────────────────────────────────┐│
13//! │ │ BTreeMap<usize, Entry> │ │ VECSXP (R_PreserveObject'd) ││
14//! │ │ ────────────────────── │ │ ───────────────────────────── ││
15//! │ │ sexp_a → {count:2, i:0}│◄──┤ [0]: sexp_a ││
16//! │ │ sexp_b → {count:1, i:1}│◄──┤ [1]: sexp_b ││
17//! │ │ sexp_c → {count:1, i:2}│◄──┤ [2]: sexp_c ││
18//! │ └─────────────────────────┘ │ [3]: <free> ││
19//! │ └───────────────────────────────────┘│
20//! └─────────────────────────────────────────────────────────────────────┘
21//! ```
22//!
23//! # Available Types
24//!
25//! | Type | Storage | Use Case |
26//! |------|---------|----------|
27//! | [`RefCountedArena`] | `RefCell` | General purpose, ordered, ppsize-scale workloads |
28//! | [`ThreadLocalArena`] | `thread_local` | Lowest overhead — no `RefCell` borrow checking |
29//!
30//! Only these two flavors are instantiated anywhere in the tree today. This
31//! module previously also shipped HashMap- and ahash-backed variants
32//! (`HashMapArena`, `ThreadLocalHashArena`, `FastHashMapArena`,
33//! `ThreadLocalFastHashArena`) behind a generic `MapStorage` abstraction; they
34//! were removed for having zero production or test consumers (see
35//! <https://github.com/a2-ai/miniextendr/issues/1138>).
36//! Re-add a flavor (and the generic `MapStorage` plumbing needed to support
37//! more than one map type again) when a real consumer appears.
3839use crate::sys::{R_PreserveObject, R_ReleaseObject, Rf_allocVector, Rf_protect, Rf_unprotect};
40use crate::{R_xlen_t, SEXP, SEXPTYPE, SexpExt};
41use std::cell::RefCell;
42use std::collections::BTreeMap;
43use std::marker::PhantomData;
44use std::mem::MaybeUninit;
45use std::rc::Rc;
4647// region: Entry type
4849/// Entry in the reference count map.
50#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Entry {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "Entry",
"count", &self.count, "index", &&self.index)
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Entry { }
#[automatically_derived]
impl ::core::clone::Clone for Entry {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<usize>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Entry { }Copy)]
51struct Entry {
52/// Reference count (how many times this SEXP has been protected)
53count: usize,
54/// Index in the backing VECSXP
55index: usize,
56}
57// endregion
5859// region: Core arena state (shared between RefCell and thread_local variants)
6061/// Core arena state without interior mutability.
62///
63/// This is used internally by both [`RefCountedArena`] (with `RefCell`) and
64/// [`ThreadLocalArena`] (with `UnsafeCell`).
65struct ArenaState {
66/// Map from SEXP pointer to entry
67map: MaybeUninit<BTreeMap<usize, Entry>>,
68/// Backing VECSXP (preserved via R_PreserveObject)
69backing: SEXP,
70/// Current capacity
71capacity: usize,
72/// Number of active entries
73len: usize,
74/// Free list for slot reuse
75free_list: Vec<usize>,
76/// Monotonic write cursor: next index to hand out on the fresh-slot path.
77 /// Distinct from `len` (live count) — after release cycles `len` can be
78 /// lower than the highest index ever handed out, so using `len` as the
79 /// cursor can return an index that is already on the free-list.
80next_slot: usize,
81}
8283impl ArenaState {
84/// Initial capacity for the backing VECSXP.
85 ///
86 /// This is suitable for light usage (a handful of protected values).
87 /// For ppsize-scale workloads (hundreds or thousands of protected values),
88 /// use [`RefCountedArena::with_capacity`] or
89 /// [`ThreadLocalArena::init_with_capacity`] to avoid repeated backing
90 /// VECSXP growth and map rehashing.
91const INITIAL_CAPACITY: usize = 16;
9293/// Maximum capacity: the backing VECSXP is indexed by `R_xlen_t` (isize),
94 /// so the capacity must fit in a non-negative `R_xlen_t`.
95const MAX_CAPACITY: usize = R_xlen_t::MAXas usize;
9697/// Convert a `usize` capacity to `R_xlen_t`, panicking on overflow.
98#[inline]
99fn capacity_as_r_xlen(cap: usize) -> R_xlen_t {
100R_xlen_t::try_from(cap).unwrap_or_else(|_| {
101{
::core::panicking::panic_fmt(format_args!("arena capacity {0} exceeds R_xlen_t::MAX ({1})",
cap, R_xlen_t::MAX));
}panic!(
102"arena capacity {} exceeds R_xlen_t::MAX ({})",
103 cap,
104 R_xlen_t::MAX
105 )106 })
107 }
108109/// Create uninitialized state (for thread_local).
110const fn uninit() -> Self {
111Self {
112 map: MaybeUninit::uninit(),
113 backing: SEXP::null(),
114 capacity: 0,
115 len: 0,
116 free_list: Vec::new(),
117 next_slot: 0,
118 }
119 }
120121/// Initialize the state.
122 ///
123 /// # Safety
124 ///
125 /// Must be called exactly once before using the state.
126unsafe fn init(&mut self, capacity: usize) {
127let capacity = capacity.max(1);
128if !(capacity <= Self::MAX_CAPACITY) {
{
::core::panicking::panic_fmt(format_args!("arena capacity {0} exceeds R_xlen_t::MAX ({1})",
capacity, R_xlen_t::MAX));
}
};assert!(
129 capacity <= Self::MAX_CAPACITY,
130"arena capacity {} exceeds R_xlen_t::MAX ({})",
131 capacity,
132 R_xlen_t::MAX
133 );
134unsafe {
135let r_cap = Self::capacity_as_r_xlen(capacity);
136let backing = Rf_protect(Rf_allocVector(SEXPTYPE::VECSXP, r_cap));
137R_PreserveObject(backing);
138Rf_unprotect(1);
139140self.map.write(BTreeMap::new());
141self.backing = backing;
142self.capacity = capacity;
143self.len = 0;
144self.next_slot = 0;
145self.free_list = Vec::with_capacity(capacity);
146 }
147 }
148149/// Create initialized state.
150unsafe fn new(capacity: usize) -> Self {
151let capacity = capacity.max(1);
152let mut state = Self {
153 map: MaybeUninit::new(BTreeMap::new()),
154 backing: SEXP::null(),
155 capacity: 0,
156 len: 0,
157 next_slot: 0,
158 free_list: Vec::with_capacity(capacity),
159 };
160unsafe { state.init_backing(capacity) };
161state162 }
163164/// Initialize just the backing (map already initialized).
165unsafe fn init_backing(&mut self, capacity: usize) {
166let capacity = capacity.max(1);
167if !(capacity <= Self::MAX_CAPACITY) {
{
::core::panicking::panic_fmt(format_args!("arena capacity {0} exceeds R_xlen_t::MAX ({1})",
capacity, R_xlen_t::MAX));
}
};assert!(
168 capacity <= Self::MAX_CAPACITY,
169"arena capacity {} exceeds R_xlen_t::MAX ({})",
170 capacity,
171 R_xlen_t::MAX
172 );
173unsafe {
174let r_cap = Self::capacity_as_r_xlen(capacity);
175let backing = Rf_protect(Rf_allocVector(SEXPTYPE::VECSXP, r_cap));
176R_PreserveObject(backing);
177Rf_unprotect(1);
178179self.backing = backing;
180self.capacity = capacity;
181 }
182 }
183184/// Get a reference to the map.
185#[inline]
186fn map(&self) -> &BTreeMap<usize, Entry> {
187// SAFETY: Map is initialized before any access
188unsafe { self.map.assume_init_ref() }
189 }
190191/// Get a mutable reference to the map.
192#[inline]
193fn map_mut(&mut self) -> &mut BTreeMap<usize, Entry> {
194// SAFETY: Map is initialized before any access
195unsafe { self.map.assume_init_mut() }
196 }
197198/// Decrement the count for a key and remove if zero.
199 ///
200 /// Returns `Some((true, index))` if the entry was found and removed,
201 /// `Some((false, index))` if the entry was found but count > 0 after
202 /// decrement, `None` if the entry was not found.
203#[inline]
204fn decrement_and_maybe_remove(&mut self, key: &usize) -> Option<(bool, usize)> {
205let map = self.map_mut();
206if let Some(entry) = map.get_mut(key) {
207entry.count -= 1;
208if entry.count == 0 {
209let index = entry.index;
210map.remove(key);
211Some((true, index))
212 } else {
213Some((false, entry.index))
214 }
215 } else {
216None217 }
218 }
219220/// Protect a SEXP from garbage collection.
221 ///
222 /// # Safety
223 ///
224 /// Must be called from the R main thread. The SEXP must be valid.
225#[inline]
226unsafe fn protect(&mut self, x: SEXP) -> SEXP {
227if x.is_nil() {
228return x;
229 }
230231let key = x.0 as usize;
232233if let Some(entry) = self.map_mut().get_mut(&key) {
234entry.count += 1;
235 } else {
236let index = self.allocate_slot();
237self.backing.set_vector_elt(indexas R_xlen_t, x);
238self.map_mut().insert(key, Entry { count: 1, index });
239self.len += 1;
240 }
241242x243 }
244245/// Unprotect a SEXP, allowing garbage collection when refcount reaches zero.
246 ///
247 /// # Safety
248 ///
249 /// Must be called from the R main thread. The SEXP must have been
250 /// previously protected by this arena.
251#[inline]
252unsafe fn unprotect(&mut self, x: SEXP) {
253if x.is_nil() {
254return;
255 }
256257let key = x.0 as usize;
258259match self.decrement_and_maybe_remove(&key) {
260Some((true, index)) => {
261// Entry was removed (count reached 0)
262self.backing.set_vector_elt(indexas R_xlen_t, SEXP::nil());
263self.free_list.push(index);
264self.len -= 1;
265 }
266Some((false, _)) => {
267// Entry still exists (count > 0)
268}
269None => {
270{
::core::panicking::panic_fmt(format_args!("unprotect called on SEXP not protected by this arena"));
};panic!("unprotect called on SEXP not protected by this arena");
271 }
272 }
273 }
274275/// Try to unprotect a SEXP. Returns false if not protected by this arena.
276 ///
277 /// # Safety
278 ///
279 /// Must be called from the R main thread.
280#[inline]
281unsafe fn try_unprotect(&mut self, x: SEXP) -> bool {
282if x.is_nil() {
283return false;
284 }
285286let key = x.0 as usize;
287288match self.decrement_and_maybe_remove(&key) {
289Some((true, index)) => {
290// Entry was removed (count reached 0)
291self.backing.set_vector_elt(indexas R_xlen_t, SEXP::nil());
292self.free_list.push(index);
293self.len -= 1;
294true
295}
296Some((false, _)) => {
297// Entry still exists (count > 0)
298true
299}
300None => false,
301 }
302 }
303304#[inline]
305/// Returns true if this arena currently protects `x`.
306fn is_protected(&self, x: SEXP) -> bool {
307if x.is_nil() {
308return false;
309 }
310let key = x.0 as usize;
311self.map().contains_key(&key)
312 }
313314#[inline]
315/// Returns the current reference count for `x` in this arena.
316 ///
317 /// Returns 0 if `x` is not protected or is `SEXP::nil()`.
318fn ref_count(&self, x: SEXP) -> usize {
319if x.is_nil() {
320return 0;
321 }
322let key = x.0 as usize;
323self.map().get(&key).map(|e| e.count).unwrap_or(0)
324 }
325326fn allocate_slot(&mut self) -> usize {
327if let Some(index) = self.free_list.pop() {
328return index;
329 }
330331if self.next_slot >= self.capacity {
332unsafe { self.grow() };
333 }
334335let idx = self.next_slot;
336self.next_slot += 1;
337idx338 }
339340unsafe fn grow(&mut self) {
341let old_capacity = self.capacity;
342let new_capacity = old_capacity343 .checked_mul(2)
344 .expect("arena capacity overflow during growth");
345if !(new_capacity <= Self::MAX_CAPACITY) {
{
::core::panicking::panic_fmt(format_args!("arena capacity {0} would exceed R_xlen_t::MAX ({1}) after growth",
new_capacity, R_xlen_t::MAX));
}
};assert!(
346 new_capacity <= Self::MAX_CAPACITY,
347"arena capacity {} would exceed R_xlen_t::MAX ({}) after growth",
348 new_capacity,
349 R_xlen_t::MAX
350 );
351let old_backing = self.backing;
352353unsafe {
354let r_new_cap = Self::capacity_as_r_xlen(new_capacity);
355let new_backing = Rf_protect(Rf_allocVector(SEXPTYPE::VECSXP, r_new_cap));
356R_PreserveObject(new_backing);
357358for i in 0..old_capacity {
359let r_i = Self::capacity_as_r_xlen(i);
360let elt = old_backing.vector_elt(r_i);
361 new_backing.set_vector_elt(r_i, elt);
362 }
363364R_ReleaseObject(old_backing);
365Rf_unprotect(1);
366367self.backing = new_backing;
368self.capacity = new_capacity;
369 }
370 }
371372/// Clear all protected values from the arena.
373 ///
374 /// # Safety
375 ///
376 /// Must be called from the R main thread.
377unsafe fn clear(&mut self) {
378for entry in self.map().values() {
379self.backing
380 .set_vector_elt(entry.index as R_xlen_t, SEXP::nil());
381 }
382self.map_mut().clear();
383self.free_list.clear();
384self.len = 0;
385self.next_slot = 0;
386 }
387388unsafe fn release_backing(&mut self) {
389if !self.backing.is_null_ptr() {
390unsafe { R_ReleaseObject(self.backing) };
391self.backing = SEXP::null();
392 }
393 }
394}
395// endregion
396397// region: RefCountedArena - RefCell-based arena
398399/// Enforces `!Send + !Sync` (R API is not thread-safe).
400type NoSendSync = PhantomData<Rc<()>>;
401402/// A reference-counted arena for GC protection, backed by a `BTreeMap`.
403///
404/// This provides an alternative to R's PROTECT stack that:
405/// - Uses reference counting for each SEXP
406/// - Allows releasing protections in any order
407/// - Has no stack size limit (uses heap allocation)
408pub struct RefCountedArena {
409 state: RefCell<ArenaState>,
410 _nosend: NoSendSync,
411}
412413impl RefCountedArena {
414/// Create a new arena with default capacity (16 slots).
415 ///
416 /// For workloads protecting many distinct SEXPs (e.g., ppsize-scale loops),
417 /// prefer [`with_capacity`](Self::with_capacity) to avoid backing VECSXP
418 /// growth and map rehashing during operation.
419 ///
420 /// # Safety
421 ///
422 /// Must be called from the R main thread. There is deliberately no
423 /// `Default` impl: constructing an arena allocates an R VECSXP (via
424 /// `Rf_allocVector` / `R_PreserveObject`) and is an unsafe, main-thread-only
425 /// act, not a safe default (#1096).
426pub unsafe fn new() -> Self {
427unsafe { Self::with_capacity(ArenaState::INITIAL_CAPACITY) }
428 }
429430/// Create a new arena with specific initial capacity.
431 ///
432 /// Pre-sizing the arena avoids growth of the backing VECSXP and rehashing
433 /// of the internal map. Use this when the expected number of distinct
434 /// protected values is known or can be estimated.
435 ///
436 /// # Safety
437 ///
438 /// Must be called from the R main thread.
439pub unsafe fn with_capacity(capacity: usize) -> Self {
440Self {
441 state: RefCell::new(unsafe { ArenaState::new(capacity) }),
442 _nosend: PhantomData,
443 }
444 }
445446/// Protect a SEXP, incrementing its reference count.
447 ///
448 /// # Safety
449 ///
450 /// Must be called from the R main thread.
451#[inline]
452pub unsafe fn protect(&self, x: SEXP) -> SEXP {
453unsafe { self.state.borrow_mut().protect(x) }
454 }
455456/// Unprotect a SEXP, decrementing its reference count.
457 ///
458 /// # Safety
459 ///
460 /// Must be called from the R main thread.
461 ///
462 /// # Panics
463 ///
464 /// Panics if `x` was not protected by this arena.
465#[inline]
466pub unsafe fn unprotect(&self, x: SEXP) {
467unsafe { self.state.borrow_mut().unprotect(x) };
468 }
469470/// Try to unprotect a SEXP, returning `true` if it was protected.
471 ///
472 /// # Safety
473 ///
474 /// Must be called from the R main thread.
475#[inline]
476pub unsafe fn try_unprotect(&self, x: SEXP) -> bool {
477unsafe { self.state.borrow_mut().try_unprotect(x) }
478 }
479480/// Check if a SEXP is currently protected by this arena.
481#[inline]
482pub fn is_protected(&self, x: SEXP) -> bool {
483self.state.borrow().is_protected(x)
484 }
485486/// Get the reference count for a SEXP (0 if not protected).
487#[inline]
488pub fn ref_count(&self, x: SEXP) -> usize {
489self.state.borrow().ref_count(x)
490 }
491492/// Get the number of distinct SEXPs currently protected.
493#[inline]
494pub fn len(&self) -> usize {
495self.state.borrow().len
496 }
497498/// Check if the arena is empty.
499#[inline]
500pub fn is_empty(&self) -> bool {
501self.state.borrow().len == 0
502}
503504/// Get the current capacity.
505#[inline]
506pub fn capacity(&self) -> usize {
507self.state.borrow().capacity
508 }
509510/// Clear all protections.
511 ///
512 /// # Safety
513 ///
514 /// Must be called from the R main thread.
515pub unsafe fn clear(&self) {
516unsafe { self.state.borrow_mut().clear() };
517 }
518519/// Protect a SEXP and return an RAII guard.
520 ///
521 /// # Safety
522 ///
523 /// Must be called from the R main thread.
524#[inline]
525pub unsafe fn guard(&self, x: SEXP) -> ArenaGuard<'_> {
526unsafe { ArenaGuard::new(self, x) }
527 }
528}
529530impl Dropfor RefCountedArena {
531fn drop(&mut self) {
532let state = self.state.get_mut();
533// SAFETY: RefCountedArena always constructs via ArenaState::new(),
534 // which initializes the map.
535unsafe { state.map.assume_init_drop() };
536unsafe { state.release_backing() };
537 }
538}
539540// endregion
541542// region: RAII Guard
543544/// An RAII guard that unprotects a SEXP when dropped.
545pub struct ArenaGuard<'a> {
546 arena: &'a RefCountedArena,
547 sexp: SEXP,
548}
549550impl<'a> ArenaGuard<'a> {
551/// Create a new guard that protects the SEXP and unprotects on drop.
552 ///
553 /// # Safety
554 ///
555 /// Must be called from the R main thread. The SEXP must be valid.
556#[inline]
557pub unsafe fn new(arena: &'a RefCountedArena, sexp: SEXP) -> Self {
558unsafe { arena.protect(sexp) };
559Self { arena, sexp }
560 }
561562#[inline]
563/// Returns the protected SEXP.
564pub fn get(&self) -> SEXP {
565self.sexp
566 }
567}
568569impl Dropfor ArenaGuard<'_> {
570fn drop(&mut self) {
571unsafe { self.arena.unprotect(self.sexp) };
572 }
573}
574575impl std::ops::Dereffor ArenaGuard<'_> {
576type Target = SEXP;
577578#[inline]
579fn deref(&self) -> &Self::Target {
580&self.sexp
581 }
582}
583// endregion
584585// region: ThreadLocalArena
586587/// State wrapper for the thread-local arena.
588struct ThreadLocalState {
589 inner: ArenaState,
590 initialized: bool,
591}
592593impl ThreadLocalState {
594/// Create an uninitialized thread-local arena state.
595 ///
596 /// Call `init` or `init_with_capacity` before use.
597const fn uninit() -> Self {
598Self {
599 inner: ArenaState::uninit(),
600 initialized: false,
601 }
602 }
603604/// Initialize with default capacity (16 slots).
605 ///
606 /// For ppsize-scale workloads, prefer [`init_with_capacity`](Self::init_with_capacity)
607 /// to avoid backing VECSXP growth and map rehashing during operation.
608 ///
609 /// # Safety
610 ///
611 /// Must be called from the R main thread. Must only be called once.
612unsafe fn init(&mut self) {
613unsafe { self.inner.init(ArenaState::INITIAL_CAPACITY) };
614self.initialized = true;
615 }
616617/// Initialize with specific capacity.
618 ///
619 /// Pre-sizing avoids growth of the backing VECSXP and rehashing of the
620 /// internal map. Use this when the expected number of distinct protected
621 /// values is known or can be estimated (e.g., the length of an input vector).
622 ///
623 /// # Safety
624 ///
625 /// Must be called from the R main thread. Must only be called once.
626unsafe fn init_with_capacity(&mut self, capacity: usize) {
627unsafe { self.inner.init(capacity) };
628self.initialized = true;
629 }
630}
631632impl Dropfor ThreadLocalState {
633fn drop(&mut self) {
634if self.initialized {
635// SAFETY: The map was initialized in init() or init_with_capacity().
636 // We must manually drop it because MaybeUninit does not run Drop.
637unsafe { self.inner.map.assume_init_drop() };
638 }
639// R backing is released separately via release_backing() if needed.
640 // Thread-local destructors may run after R has shut down, so we do NOT
641 // call R_ReleaseObject here — the R runtime owns the backing VECSXP
642 // lifetime via R_PreserveObject.
643}
644}
645646const THREAD_LOCAL_STATE:
::std::thread::LocalKey<std::cell::UnsafeCell<ThreadLocalState>> =
{
const __RUST_STD_INTERNAL_INIT:
std::cell::UnsafeCell<ThreadLocalState> =
{ std::cell::UnsafeCell::new(ThreadLocalState::uninit()) };
unsafe {
::std::thread::LocalKey::new(const {
if ::std::mem::needs_drop::<std::cell::UnsafeCell<ThreadLocalState>>()
{
|__rust_std_internal_init|
{
#[thread_local]
static __RUST_STD_INTERNAL_VAL:
::std::thread::local_impl::EagerStorage<std::cell::UnsafeCell<ThreadLocalState>>
=
::std::thread::local_impl::EagerStorage::new(__RUST_STD_INTERNAL_INIT);
__RUST_STD_INTERNAL_VAL.get()
}
} else {
|__rust_std_internal_init|
{
#[thread_local]
static __RUST_STD_INTERNAL_VAL:
std::cell::UnsafeCell<ThreadLocalState> =
__RUST_STD_INTERNAL_INIT;
&__RUST_STD_INTERNAL_VAL
}
}
})
}
};thread_local! {
647static THREAD_LOCAL_STATE: std::cell::UnsafeCell<ThreadLocalState> =
648const { std::cell::UnsafeCell::new(ThreadLocalState::uninit()) };
649}650651/// Thread-local, `BTreeMap`-backed reference-counted GC protection arena.
652///
653/// This provides the lowest overhead for protection operations by
654/// eliminating `RefCell` borrow checking — each thread gets its own
655/// `ThreadLocalState` (private) accessed through an `UnsafeCell`.
656///
657/// ```ignore
658/// use miniextendr_api::refcount_protect::ThreadLocalArena;
659/// unsafe { ThreadLocalArena::protect(x) };
660/// ```
661pub struct ThreadLocalArena;
662663impl ThreadLocalArena {
664/// Access the thread-local state.
665#[inline]
666fn with_state<R, F: FnOnce(&mut ThreadLocalState) -> R>(f: F) -> R {
667THREAD_LOCAL_STATE.with(|cell| f(unsafe { &mut *cell.get() }))
668 }
669670/// Initialize the arena with default capacity (called automatically on first use).
671 ///
672 /// # Safety
673 ///
674 /// Must be called from the R main thread.
675pub unsafe fn init() {
676Self::with_state(|s| {
677if !s.initialized {
678unsafe { s.init() };
679 }
680 });
681 }
682683/// Initialize the arena with specific capacity.
684 ///
685 /// Use this when you know the expected number of distinct protected values
686 /// to avoid backing VECSXP growth and map rehashing during operation.
687 ///
688 /// If already initialized, this is a no-op.
689 ///
690 /// # Safety
691 ///
692 /// Must be called from the R main thread.
693pub unsafe fn init_with_capacity(capacity: usize) {
694Self::with_state(|s| {
695if !s.initialized {
696unsafe { s.init_with_capacity(capacity) };
697 }
698 });
699 }
700701/// Protect a SEXP, incrementing its reference count.
702 ///
703 /// # Safety
704 ///
705 /// Must be called from the R main thread.
706#[inline]
707pub unsafe fn protect(x: SEXP) -> SEXP {
708Self::with_state(|s| {
709if !s.initialized {
710unsafe { s.init() };
711 }
712unsafe { s.inner.protect(x) }
713 })
714 }
715716/// Unprotect a SEXP.
717 ///
718 /// # Safety
719 ///
720 /// Must be called from the R main thread.
721#[inline]
722pub unsafe fn unprotect(x: SEXP) {
723Self::with_state(|s| {
724// If the arena was never initialized, no SEXP could have been
725 // protected by it, so there is nothing to unprotect.
726if !s.initialized {
727return;
728 }
729unsafe { s.inner.unprotect(x) };
730 });
731 }
732733/// Try to unprotect a SEXP.
734 ///
735 /// # Safety
736 ///
737 /// Must be called from the R main thread.
738#[inline]
739pub unsafe fn try_unprotect(x: SEXP) -> bool {
740Self::with_state(|s| {
741// If the arena was never initialized, no SEXP could have been
742 // protected by it, so return false.
743if !s.initialized {
744return false;
745 }
746unsafe { s.inner.try_unprotect(x) }
747 })
748 }
749750/// Protect without checking initialization.
751 ///
752 /// For hot loops where `init()` or `init_with_capacity()` has already been called.
753 ///
754 /// # Safety
755 ///
756 /// - Must be called from the R main thread.
757 /// - The arena must have been initialized via `init()` or `init_with_capacity()`.
758#[inline]
759pub unsafe fn protect_fast(x: SEXP) -> SEXP {
760Self::with_state(|s| {
761if true {
if !s.initialized {
{
::core::panicking::panic_fmt(format_args!("protect_fast called before init"));
}
};
};debug_assert!(s.initialized, "protect_fast called before init");
762unsafe { s.inner.protect(x) }
763 })
764 }
765766/// Unprotect without checking initialization.
767 ///
768 /// For hot loops where `init()` or `init_with_capacity()` has already been called.
769 ///
770 /// # Safety
771 ///
772 /// - Must be called from the R main thread.
773 /// - The arena must have been initialized via `init()` or `init_with_capacity()`.
774#[inline]
775pub unsafe fn unprotect_fast(x: SEXP) {
776Self::with_state(|s| {
777if true {
if !s.initialized {
{
::core::panicking::panic_fmt(format_args!("unprotect_fast called before init"));
}
};
};debug_assert!(s.initialized, "unprotect_fast called before init");
778unsafe { s.inner.unprotect(x) };
779 });
780 }
781782/// Try to unprotect without checking initialization.
783 ///
784 /// For hot loops where `init()` or `init_with_capacity()` has already been called.
785 ///
786 /// # Safety
787 ///
788 /// - Must be called from the R main thread.
789 /// - The arena must have been initialized via `init()` or `init_with_capacity()`.
790#[inline]
791pub unsafe fn try_unprotect_fast(x: SEXP) -> bool {
792Self::with_state(|s| {
793if true {
if !s.initialized {
{
::core::panicking::panic_fmt(format_args!("try_unprotect_fast called before init"));
}
};
};debug_assert!(s.initialized, "try_unprotect_fast called before init");
794unsafe { s.inner.try_unprotect(x) }
795 })
796 }
797798/// Check if a SEXP is protected.
799#[inline]
800pub fn is_protected(x: SEXP) -> bool {
801Self::with_state(|s| {
802if !s.initialized {
803return false;
804 }
805s.inner.is_protected(x)
806 })
807 }
808809/// Get reference count.
810#[inline]
811pub fn ref_count(x: SEXP) -> usize {
812Self::with_state(|s| {
813if !s.initialized {
814return 0;
815 }
816s.inner.ref_count(x)
817 })
818 }
819820/// Number of protected SEXPs.
821#[inline]
822pub fn len() -> usize {
823Self::with_state(|s| s.inner.len)
824 }
825826/// Check if empty.
827#[inline]
828pub fn is_empty() -> bool {
829Self::len() == 0
830}
831832/// Get capacity.
833#[inline]
834pub fn capacity() -> usize {
835Self::with_state(|s| s.inner.capacity)
836 }
837838/// Clear all protections.
839 ///
840 /// # Safety
841 ///
842 /// Must be called from the R main thread.
843pub unsafe fn clear() {
844Self::with_state(|s| {
845if s.initialized {
846unsafe { s.inner.clear() };
847 }
848 });
849 }
850}
851852// Tests are in tests/refcount_protect.rs (requires R runtime via miniextendr-engine)
853// endregion