artichoke_backend/artichoke.rs
1use std::ffi::c_void;
2use std::ops::{Deref, DerefMut};
3use std::ptr::NonNull;
4
5use crate::ffi::{self, InterpreterExtractError};
6use crate::state::State;
7use crate::sys;
8use crate::value::Value;
9
10/// Interpreter instance.
11///
12/// Functionality is added to the interpreter via traits, for example,
13/// [garbage collection](crate::gc::MrbGarbageCollection) or
14/// [eval](crate::core::Eval).
15#[derive(Debug)]
16pub struct Artichoke {
17 /// Underlying mruby interpreter.
18 ///
19 /// This is an owned reference to the interpreter via a mutable pointer.
20 pub mrb: NonNull<sys::mrb_state>,
21
22 /// Interpreter state.
23 ///
24 /// This field is an `Option` because the `State` is moved in and out of the
25 /// `Artichoke` struct as the call graph crosses between Rust and C and C to
26 /// Rust.
27 pub state: Option<Box<State>>,
28}
29
30impl Artichoke {
31 /// Create a new interpreter from an underlying `mrb` and a `State`.
32 #[must_use]
33 pub const fn new(mrb: NonNull<sys::mrb_state>, state: Box<State>) -> Self {
34 let state = Some(state);
35 Self { mrb, state }
36 }
37
38 /// Prevent the given value from being garbage collected.
39 ///
40 /// Calls [`sys::mrb_gc_protect`] on this value which adds it to the GC
41 /// arena. This object will remain in the arena until [`ArenaIndex::restore`]
42 /// restores the arena to an index before this call to protect.
43 ///
44 /// [`ArenaIndex::restore`]: crate::gc::arena::ArenaIndex::restore
45 pub fn protect(&mut self, value: Value) -> Value {
46 // SAFETY: `mrb_gc_protect` is called with a valid `mrb_state` and `mrb_value`.
47 unsafe {
48 let value = value.inner();
49 let _ = self.with_ffi_boundary(|mrb| sys::mrb_gc_protect(mrb, value));
50 }
51 value
52 }
53
54 /// Execute a a closure by moving the [`State`] into the `mrb` instance.
55 ///
56 /// This method prepares this interpreter to cross an FFI boundary. When the
57 /// Artichoke implementation calls mruby FFI functions, the `State` must be
58 /// moved into the [`sys::mrb_state`] userdata pointer.
59 ///
60 /// # Safety
61 ///
62 /// This method moves the `State` out of this instance into the `mrb`
63 /// instance. During this function's execution, this instance may be
64 /// partially initialized.
65 ///
66 /// This function is only safe to call if the closure only calls FFI
67 /// functions that use a raw `*mut sys::mrb_state`.
68 pub unsafe fn with_ffi_boundary<F, T>(&mut self, func: F) -> Result<T, InterpreterExtractError>
69 where
70 F: FnOnce(*mut sys::mrb_state) -> T,
71 {
72 if let Some(state) = self.state.take() {
73 // SAFETY: Ensure we don't create multiple mutable references by
74 // moving the `mrb` out of the `Artichoke` and converting to a raw
75 // pointer.
76 //
77 // 1. Extract a `*mut sys::mrb_state` pointer from the `NonNull`
78 // `mrb` field.
79 // 2. Function preconditions declare that `Artichoke` is not accessed
80 // inside the closure.
81 // 3. Rust borrowing rules enforce that `Artichoke` is not accessed
82 // inside the closure.
83 // 4. This function moves the `State` into the `mrb`.
84 // 5. If `mrb` re-enters `Artichoke` via trampoline, a new
85 // `Artichoke` is made by moving the `State` out of the `mrb`.
86 // 6. The `Artichoke` in the FFI entry point is wrapped in a `Guard`.
87 // 7. On drop, `Guard` moves the `State` back into the `mrb`.
88 // 8. On return from `mrb`, here, extract the `State` which should be
89 // moved back into the `mrb`.
90 // 9. Replace `self` with the new interpreter.
91
92 // Step 1
93 let mrb = self.mrb.as_ptr();
94
95 // Step 4
96 //
97 // SAFETY: The `State` is moved into the `mrb` userdata pointer.
98 unsafe {
99 (*mrb).ud = Box::into_raw(state).cast::<c_void>();
100 }
101
102 // Steps 5-7
103 let result = func(mrb);
104
105 // Step 8
106 //
107 // SAFETY: The `State` was previously moved into of the `mrb`
108 // userdata pointer.
109 let extracted = unsafe { ffi::from_user_data(mrb)? };
110
111 // Step 9
112 self.state = extracted.state;
113 Ok(result)
114 } else {
115 Err(InterpreterExtractError::new())
116 }
117 }
118
119 /// Consume an interpreter and return the pointer to the underlying
120 /// [`sys::mrb_state`].
121 ///
122 /// This function does not free any interpreter resources. Its intended use
123 /// is to prepare the interpreter to cross over an FFI boundary.
124 ///
125 /// This is an associated function and must be called as
126 /// `Artichoke::into_raw(interp)`.
127 ///
128 /// # Safety
129 ///
130 /// After calling this function, the caller is responsible for properly
131 /// freeing the memory occupied by the interpreter heap. The easiest way to
132 /// do this is to call [`ffi::from_user_data`] with the returned pointer and
133 /// then call [`Artichoke::close`].
134 #[must_use]
135 pub unsafe fn into_raw(mut interp: Self) -> *mut sys::mrb_state {
136 let mut guard = Guard::new(&mut interp);
137 guard.interp().mrb.as_ptr()
138 }
139
140 /// Consume an interpreter and free all live objects.
141 pub fn close(mut self) {
142 // SAFETY: It is permissible to directly access the `*mut sys::mrb_state`
143 // because we are tearing down the interpreter. The only `MRB_API` calls
144 // made from this point are related to freeing interpreter memory.
145 let mrb = unsafe { self.mrb.as_mut() };
146 if let Some(state) = self.state.take() {
147 // Do not free class and module specs before running the final
148 // garbage collection on `mrb_close`.
149 let State {
150 parser,
151 classes,
152 modules,
153 ..
154 } = *state;
155
156 // SAFETY: This deallocation and drop order ensures that no dangling
157 // references and pointers are visible during tear-down:
158 //
159 // - The parser must be deallocated to free the associated
160 // `mrbc_context`.
161 // - The parser must be freed before the `mrb_state` because the
162 // `mrb_state` may hold a copy of the context pointer.
163 // - `classes` and `modules` from the Artichoke Rust `State`
164 // must be live allocations before calling `mrb_close` because
165 // these registries allow resolving the `dfree` free functions
166 // for Ruby types defined with type tag `MRB_TT_CDATA`.
167 unsafe {
168 if let Some(parser) = parser {
169 parser.close(mrb);
170 }
171 sys::mrb_close(mrb);
172 }
173
174 drop(classes);
175 drop(modules);
176 } else {
177 // SAFETY: If there is no Artichoke Rust `State`, the mruby
178 // interpreter cannot be safely closed. Prefer to leak the
179 // interpreter than try to close it.
180 let _ = mrb;
181 }
182 }
183}
184
185/// Interpreter guard that prepares an [`Artichoke`] to re-enter an FFI
186/// boundary.
187///
188/// Artichoke integrates with the mruby VM via many `extern "C" fn` trampolines
189/// that are invoked by mruby to run some portion of the VM in Rust.
190///
191/// These trampolines typically require an [`Artichoke`] interpreter to do
192/// useful work, so they move the [`State`](crate::state::State) out of the
193/// `mrb` userdata pointer into an `Artichoke` struct.
194///
195/// To ensure safety, the `State` must be moved back into the `mrb` userdata
196/// pointer before re-entering the FFI boundary. This guard implements [`Drop`]
197/// to re-serialize the `State` into the `mrb` once it goes out of scope.
198///
199/// `Guard` is passed directly to [`error::raise`](crate::error::raise).
200#[derive(Debug)]
201pub struct Guard<'a>(&'a mut Artichoke);
202
203impl<'a> Guard<'a> {
204 /// Create a new guard that wraps an interpreter.
205 ///
206 /// This function is most effective when the interpreter is temporarily
207 /// created from a source `mrb_state` and stored on the stack.
208 pub fn new(interp: &'a mut Artichoke) -> Self {
209 Self(interp)
210 }
211
212 /// Access the inner guarded interpreter.
213 ///
214 /// The interpreter is also accessible via [`Deref`], [`DerefMut`],
215 /// [`AsRef`], and [`AsMut`].
216 #[inline]
217 pub fn interp(&mut self) -> &mut Artichoke {
218 self.0
219 }
220}
221
222impl Deref for Guard<'_> {
223 type Target = Artichoke;
224
225 #[inline]
226 fn deref(&self) -> &Self::Target {
227 &*self.0
228 }
229}
230
231impl DerefMut for Guard<'_> {
232 #[inline]
233 fn deref_mut(&mut self) -> &mut Self::Target {
234 self.0
235 }
236}
237
238impl Drop for Guard<'_> {
239 fn drop(&mut self) {
240 let state = self.0.state.take();
241 let state = state.unwrap_or_else(|| {
242 emit_fatal_warning!("Dropping Guard with no State");
243 panic!("Dropping Guard with no State")
244 });
245
246 // SAFETY: The `Guard` ensures that the `mrb_state` is valid and
247 // initialized. When `state` is `Some`, the `State` is moved out of the
248 // userdata pointer, so we can now move it back in.
249 unsafe {
250 let mrb = self.0.mrb.as_ptr();
251 (*mrb).ud = Box::into_raw(state).cast::<c_void>();
252 }
253 }
254}