artichoke_backend/gc/
arena.rs

1//! Garbage collection arenas for native code.
2
3use std::borrow::Cow;
4use std::error;
5use std::fmt;
6use std::ops::{Deref, DerefMut};
7
8use spinoso_exception::Fatal;
9
10use crate::Artichoke;
11use crate::core::{ClassRegistry, TryConvertMut};
12use crate::error::{Error, RubyException};
13use crate::sys;
14
15/// Failed to create a new GC arena savepoint.
16///
17/// This error is returned by [`ArenaIndex::new`].
18#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
19pub struct ArenaSavepointError {
20    _private: (),
21}
22
23impl ArenaSavepointError {
24    /// Constructs a new, default `ArenaSavepointError`.
25    #[must_use]
26    pub const fn new() -> Self {
27        Self { _private: () }
28    }
29}
30
31impl fmt::Display for ArenaSavepointError {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.write_str("Failed to create internal garbage collection savepoint")
34    }
35}
36
37impl error::Error for ArenaSavepointError {}
38
39impl RubyException for ArenaSavepointError {
40    fn message(&self) -> Cow<'_, [u8]> {
41        Cow::Borrowed(b"Failed to create internal garbage collection savepoint")
42    }
43
44    fn name(&self) -> Cow<'_, str> {
45        "fatal".into()
46    }
47
48    fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
49        let _ = interp;
50        None
51    }
52
53    fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
54        let message = interp.try_convert_mut(self.message()).ok()?;
55        let value = interp.new_instance::<Fatal>(&[message]).ok().flatten()?;
56        Some(value.inner())
57    }
58}
59
60impl From<ArenaSavepointError> for Error {
61    fn from(exception: ArenaSavepointError) -> Self {
62        let err: Box<dyn RubyException> = Box::new(exception);
63        Self::from(err)
64    }
65}
66
67/// Interpreter guard that acts as a GC arena savepoint.
68///
69/// Arena savepoints ensure mruby objects are reaped even when allocated with
70/// the C API.
71///
72/// mruby manages objects created via the C API in a memory construct called
73/// the [arena]. The arena is a stack and objects stored there are permanently
74/// alive to avoid having to track lifetimes externally to the interpreter.
75///
76/// An [`ArenaIndex`] is an index to some position of the stack. When restoring
77/// an `ArenaIndex`, the stack pointer is moved. All objects beyond the pointer
78/// are no longer live and are eligible to be collected at the next GC.
79///
80/// `ArenaIndex` implements [`Drop`], so letting it go out of scope is
81/// sufficient to ensure objects get collected eventually.
82///
83/// [arena]: https://github.com/mruby/mruby/blob/master/doc/guides/gc-arena-howto.md
84#[derive(Debug)]
85pub struct ArenaIndex<'a> {
86    index: i32,
87    interp: &'a mut Artichoke,
88}
89
90impl<'a> ArenaIndex<'a> {
91    /// Create a new arena savepoint.
92    pub fn new(interp: &'a mut Artichoke) -> Result<Self, ArenaSavepointError> {
93        unsafe {
94            interp
95                .with_ffi_boundary(|mrb| sys::mrb_sys_gc_arena_save(mrb))
96                .map(move |index| Self { index, interp })
97                .map_err(|_| ArenaSavepointError::new())
98        }
99    }
100
101    /// Restore the arena stack pointer to its prior index.
102    pub fn restore(self) {
103        drop(self);
104    }
105
106    /// Access the inner guarded interpreter.
107    ///
108    /// The interpreter is also accessible via [`Deref`], [`DerefMut`],
109    /// [`AsRef`], and [`AsMut`].
110    #[inline]
111    pub fn interp(&mut self) -> &mut Artichoke {
112        self.interp
113    }
114}
115
116impl Deref for ArenaIndex<'_> {
117    type Target = Artichoke;
118
119    #[inline]
120    fn deref(&self) -> &Self::Target {
121        &*self.interp
122    }
123}
124
125impl DerefMut for ArenaIndex<'_> {
126    #[inline]
127    fn deref_mut(&mut self) -> &mut Self::Target {
128        self.interp
129    }
130}
131
132impl Drop for ArenaIndex<'_> {
133    fn drop(&mut self) {
134        let idx = self.index;
135        // We can't panic in a drop impl, so ignore errors when crossing the
136        // FFI boundary.
137        let _ignored = unsafe {
138            self.interp
139                .with_ffi_boundary(|mrb| sys::mrb_sys_gc_arena_restore(mrb, idx))
140        };
141    }
142}