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