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}