Skip to main content

luaur_rt/
exec_raw.rs

1//! [`Lua::exec_raw`] and [`Lua::create_c_function`] — escape hatches to the raw
2//! luaur stack machine. Mirror `mlua::Lua::exec_raw` / `create_c_function`.
3//!
4//! `exec_raw` runs a user closure that manipulates the raw stack **inside a
5//! protected call**, so a `lua_error` raised by the closure (which luaur
6//! implements as a `panic_any(lua_exception)`) is caught by the VM's own
7//! `lua_pcall` and surfaced as an [`Error`], exactly like a normal Lua error.
8//! Unlike the [`create_function`](crate::Lua::create_function) trampoline, the
9//! `exec_raw` trampoline deliberately does **not** `catch_unwind`: the whole
10//! point is to let the VM's protected-call machinery handle the unwind.
11
12use std::cell::Cell;
13
14use crate::error::Result;
15use crate::function::Function;
16use crate::multi::MultiValue;
17use crate::state::Lua;
18use crate::sys::*;
19use crate::traits::{FromLuaMulti, IntoLuaMulti};
20
21/// The boxed, type-erased raw closure stored in the `exec_raw` trampoline's
22/// upvalue userdata. `FnMut`-once: it is taken out and run exactly once.
23type RawFn = Box<dyn FnOnce(*mut lua_State)>;
24
25/// Userdata storage for the `exec_raw` closure (a `Cell<Option<..>>` so the
26/// trampoline can `take` it).
27struct RawFnSlot(Cell<Option<RawFn>>);
28
29/// Destructor: drop the (possibly already-taken) closure box.
30unsafe extern "C" fn raw_fn_dtor(ptr: *mut c_void) {
31    if !ptr.is_null() {
32        unsafe { core::ptr::drop_in_place(ptr as *mut RawFnSlot) };
33    }
34}
35
36/// The trampoline for `exec_raw`: recover the boxed closure from upvalue 1 and
37/// run it on the calling state. Does NOT `catch_unwind` — a `lua_error` from the
38/// closure must propagate to the enclosing `lua_pcall`.
39unsafe fn exec_raw_trampoline(state: *mut lua_State) -> c_int {
40    unsafe {
41        let ud = lua_touserdata(state, lua_upvalueindex(1));
42        if ud.is_null() {
43            return 0;
44        }
45        let slot = &*(ud as *const RawFnSlot);
46        let f = slot.0.take();
47        let base = lua_gettop(state);
48        if let Some(f) = f {
49            f(state);
50        }
51        // Everything the closure left above the stack base is a result.
52        let top = lua_gettop(state);
53        (top - base).max(0)
54    }
55}
56
57fn exec_raw_trampoline_ptr() -> lua_CFunction {
58    Some(exec_raw_trampoline)
59}
60
61impl Lua {
62    /// Run a closure that manipulates the raw luaur stack, under a protected
63    /// call. Mirrors `mlua::Lua::exec_raw`.
64    ///
65    /// `args` are pushed first (as the function arguments); then `f` runs with
66    /// the raw `*mut lua_State`, pushing any results it wants returned. A
67    /// `lua_error` raised inside `f` is caught and returned as an [`Error`].
68    ///
69    /// # Safety
70    /// `f` operates on the raw stack with no safety net beyond the protected
71    /// call; it must leave the stack in a consistent state (push results, not
72    /// underflow). This mirrors `mlua::Lua::exec_raw`'s `unsafe` contract.
73    pub unsafe fn exec_raw<R, F>(&self, args: impl IntoLuaMulti, f: F) -> Result<R>
74    where
75        R: FromLuaMulti,
76        F: FnOnce(*mut lua_State),
77    {
78        let state = self.state();
79        let args: MultiValue = args.into_lua_multi(self)?;
80        // Erase the closure's lifetime: it runs to completion before this
81        // function returns (synchronous protected call), so the closure (and
82        // anything it borrows) outlives the call.
83        let boxed: RawFn = {
84            // SAFETY: `f` is consumed within this synchronous call frame (the
85            // protected call below runs it to completion before returning), so
86            // the closure — and anything it borrows — outlives the box. The
87            // transmute only widens the closure's (non-`'static`) lifetime to
88            // `'static`; that `'static` box never escapes this function.
89            let f: Box<dyn FnOnce(*mut lua_State) + '_> = Box::new(f);
90            unsafe { core::mem::transmute::<Box<dyn FnOnce(*mut lua_State) + '_>, RawFn>(f) }
91        };
92        unsafe {
93            let nargs = args.len() as c_int;
94            if lua_checkstack(state, nargs.saturating_add(2)) == 0 {
95                return Err(crate::error::Error::runtime("stack overflow in exec_raw"));
96            }
97            // Allocate the slot userdata and write the closure into it.
98            let storage =
99                lua_newuserdatadtor(state, core::mem::size_of::<RawFnSlot>(), Some(raw_fn_dtor));
100            if storage.is_null() {
101                return Err(crate::error::Error::runtime(
102                    "exec_raw: failed to allocate closure userdata",
103                ));
104            }
105            core::ptr::write(storage as *mut RawFnSlot, RawFnSlot(Cell::new(Some(boxed))));
106            // Wrap it in a C closure (consumes the userdata as upvalue 1).
107            lua_pushcclosurek(
108                state,
109                exec_raw_trampoline_ptr(),
110                c"luaur-rt-exec-raw".as_ptr(),
111                1,
112                None,
113            );
114            // Push the arguments after the function, then protected-call.
115            let base = lua_gettop(state) - 1; // index just below the function
116            for v in args.iter() {
117                self.push_value(v)?;
118            }
119            let status = lua_pcall(state, nargs, -1, 0);
120            if status != 0 {
121                return Err(self.pop_error(status));
122            }
123            // Collect results left above `base`.
124            let top = lua_gettop(state);
125            let nresults = top - base;
126            let mut results = MultiValue::with_capacity(nresults.max(0) as usize);
127            for i in 0..nresults {
128                results.push_back(self.value_from_stack(base + 1 + i)?);
129            }
130            lua_settop(state, base);
131            R::from_lua_multi(results, self)
132        }
133    }
134
135    /// Wrap a raw luaur `lua_CFunction` as a [`Function`]. Mirrors
136    /// `mlua::Lua::create_c_function`.
137    ///
138    /// **DEVIATION:** luaur's `lua_CFunction` is a plain Rust
139    /// `Option<unsafe fn(*mut lua_State) -> c_int>` (luaur is a pure-Rust VM with
140    /// no C ABI boundary), not an `extern "C-unwind" fn` as in mlua's FFI build.
141    /// The function value is otherwise identical; callers pass a luaur-shaped
142    /// `unsafe fn` (see [`ffi::lua_CFunction`](crate::sys::lua_CFunction)).
143    ///
144    /// # Safety
145    /// The supplied function runs with raw access to the `lua_State`; it must
146    /// honor the luaur calling convention (consume its arguments, push its
147    /// results, return the result count). Mirrors mlua's `unsafe` contract.
148    pub unsafe fn create_c_function(&self, func: lua_CFunction) -> Result<Function> {
149        let state = self.state();
150        unsafe {
151            lua_pushcclosurek(state, func, c"luaur-rt-c-function".as_ptr(), 0, None);
152            Ok(Function::from_ref(self.pop_ref()))
153        }
154    }
155}