luna_core/vm/ergo.rs
1//! Embedder ergonomics.
2//!
3//! `vm.eval` / `vm.eval_chunk` collapse the
4//! `load(src.as_bytes(), name.as_bytes())? → call_value(Value::Closure(cl), &[])`
5//! sequence into a single call. `vm.intern_str` exposes the heap-side
6//! string interner for embedders that need a `Gc<LuaStr>` handle
7//! (table key, set comparison, etc.).
8//!
9//! ```
10//! use luna_core::vm::Vm;
11//! use luna_core::version::LuaVersion;
12//! let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().open_math().build();
13//! let r = vm.eval("return 1 + 2").unwrap();
14//! assert_eq!(r.len(), 1);
15//! ```
16
17use crate::runtime::heap::Gc;
18use crate::runtime::string::LuaStr;
19use crate::runtime::value::Value;
20use crate::vm::error::LuaError;
21use crate::vm::exec::Vm;
22
23impl Vm {
24 /// Same as [`Vm::eval`] but with a user-supplied chunk name
25 /// (appears in tracebacks for debugging).
26 pub fn eval_chunk(&mut self, src: &str, name: &str) -> Result<Vec<Value>, LuaError> {
27 self.clear_error_metadata();
28 let cl = match self.load(src.as_bytes(), name.as_bytes()) {
29 Ok(c) => c,
30 Err(syntax) => {
31 // Classify + record source position.
32 self.set_error_kind(crate::vm::error::LuaErrorKind::Syntax);
33 self.set_error_source(name.to_string(), syntax.line);
34 // Surface SyntaxError as a LuaError carrying the
35 // formatted PUC-style message (`<line>: <msg>`).
36 let msg = format!("{}", syntax);
37 let s = self.heap.intern(msg.as_bytes());
38 return Err(LuaError(Value::Str(s)));
39 }
40 };
41 self.call_value(Value::Closure(cl), &[])
42 }
43
44 /// Intern a UTF-8 string into the heap's string table.
45 /// Idempotent — interning the same bytes twice returns the same
46 /// [`Gc<LuaStr>`] handle.
47 ///
48 /// Useful for embedders constructing table keys or comparing Lua
49 /// strings without going through `Value::Str` wrapping each time.
50 pub fn intern_str(&mut self, s: &str) -> Gc<LuaStr> {
51 self.heap.intern(s.as_bytes())
52 }
53
54 // ─── Host-root pool ─────────────────────────────────────────
55 //
56 // The slot-recycling pool keyed by `HostRootTicket { idx, generation }`
57 // (`pin_host` / `read_host` / `write_host` / `unpin` / `unpin_all` /
58 // `host_root_count`) lives in [`crate::vm::host_roots`]; the type
59 // re-exports are in [`crate::vm`] (`HostRootTicket`, `HostRootStale`).
60
61 // ─── LuaError classification ─────────────────────────────────
62 //
63 // The error value itself (`LuaError(pub Value)`) stays `Copy`.
64 // Richer context lives on the Vm; embedders read it
65 // via these accessors after observing a `Result::Err(LuaError)`.
66
67 /// Classification of the most recently raised error on this Vm.
68 /// Returns [`crate::vm::error::LuaErrorKind::Runtime`] before any error fires.
69 pub fn error_kind(&self) -> crate::vm::error::LuaErrorKind {
70 self.last_error_kind
71 }
72
73 /// `(source_name, line)` of the most recently raised error, or
74 /// `None` if the dispatcher could not locate one. Source names
75 /// match Lua's chunk-name convention (`"=eval"`, `"=stdin"`,
76 /// user-supplied via `Vm::load`).
77 pub fn error_source(&self) -> Option<(&str, u32)> {
78 self.last_error_source
79 .as_ref()
80 .map(|(s, l)| (s.as_str(), *l))
81 }
82
83 /// Set the classification for the next error to be raised — used
84 /// by the dispatcher at well-known sites. Embedders writing
85 /// native callbacks may call this before returning `Err(LuaError)`
86 /// to flag a specific kind (e.g. `LuaErrorKind::Type` for a bad
87 /// arg).
88 pub fn set_error_kind(&mut self, kind: crate::vm::error::LuaErrorKind) {
89 self.last_error_kind = kind;
90 }
91
92 /// Set the `(source_name, line)` for the next error to be raised.
93 /// The dispatcher uses this at the syntax-error / parser
94 /// boundary.
95 pub fn set_error_source(&mut self, name: String, line: u32) {
96 self.last_error_source = Some((name, line));
97 }
98
99 /// Clear error classification — called on a clean `call_value`
100 /// entry so old error metadata doesn't leak into the next call.
101 pub fn clear_error_metadata(&mut self) {
102 self.last_error_kind = crate::vm::error::LuaErrorKind::default();
103 self.last_error_source = None;
104 }
105
106 // ─── LuaUserdata host payloads ───────────────────────────────
107 //
108 // The closed-world userdata GC infrastructure (`Gc<Userdata>` +
109 // metatable + `__gc`) carries a
110 // `Host { type_id, data: Box<dyn Any> }` payload variant for
111 // embedders to stash arbitrary Rust values.
112 //
113 // Bounds are `T: LuaUserdata` so the metatable produced by
114 // `T::add_methods` is auto-installed at `create_userdata` time. A
115 // plain type needs only a one-line `impl LuaUserdata for T {}`.
116
117 /// Allocate a host userdata wrapping `value`. Returns the
118 /// `Value::Userdata` you can `set_global` / pin / pass to scripts.
119 ///
120 /// The metatable produced by [`crate::vm::LuaUserdata::add_methods`]
121 /// is auto-installed on the userdata (cached per `Vm` keyed by
122 /// `TypeId::of::<T>()`). For a type that only needs identity +
123 /// raw host-side access (no Lua-callable methods), provide an
124 /// empty impl:
125 ///
126 /// ```
127 /// # use luna_core::vm::LuaUserdata;
128 /// struct Counter(i64);
129 /// impl LuaUserdata for Counter {}
130 /// ```
131 ///
132 /// ```
133 /// use luna_core::vm::{LuaUserdata, Vm};
134 /// use luna_core::version::LuaVersion;
135 /// use luna_core::runtime::Value;
136 ///
137 /// #[derive(Debug)]
138 /// struct Counter(i64);
139 /// impl LuaUserdata for Counter {}
140 ///
141 /// let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().build();
142 /// let ud = vm.create_userdata(Counter(42));
143 /// vm.set_global("counter", ud).unwrap();
144 ///
145 /// match ud {
146 /// Value::Userdata(g) => {
147 /// // SAFETY: single-threaded heap; pointer is live.
148 /// let r = unsafe { &*g.as_ptr() };
149 /// assert_eq!(r.downcast::<Counter>().unwrap().0, 42);
150 /// }
151 /// _ => unreachable!(),
152 /// }
153 /// ```
154 pub fn create_userdata<T: crate::vm::LuaUserdata>(&mut self, value: T) -> Value {
155 // Capture a monomorphic trace adapter for `T`.
156 // The fn item `trace_fn_for::<T>` is a distinct code address
157 // per `T` (LLVM monomorphization); the downcast cannot fail
158 // because `register_userdata::<T>` pairs the adapter with
159 // `TypeId::of::<T>()` here, and `Userdata::trace` always reads
160 // the adapter back through the same `Host` instance.
161 fn trace_fn_for<T: crate::vm::LuaUserdata>(
162 any: &(dyn std::any::Any + 'static),
163 m: &mut crate::vm::UserdataMarker<'_>,
164 ) {
165 let typed = any
166 .downcast_ref::<T>()
167 .expect("LuaUserdata trace adapter / TypeId mismatch");
168 typed.trace(m);
169 }
170 let payload = crate::runtime::userdata::UserdataPayload::Host {
171 type_id: std::any::TypeId::of::<T>(),
172 data: Box::new(value),
173 trace_fn: Some(trace_fn_for::<T>),
174 };
175 let g = self.heap.new_userdata(payload, /* writable */ true);
176 // Install the trait-derived metatable (or
177 // fetch the cached one). Build only fails if the metatable's
178 // table set overflows MAX_ASIZE, which is impossible with
179 // <100 entries; expect-on-fail is appropriate here.
180 let mt = self
181 .register_userdata::<T>()
182 .expect("LuaUserdata metatable build overflowed");
183 // SAFETY: g is a freshly allocated Gc<Userdata>; the heap is
184 // single-threaded and the pointer is live.
185 unsafe { g.as_mut() }.set_metatable(Some(mt));
186 self.heap
187 .barrier_back(g.as_ptr() as *mut crate::runtime::heap::GcHeader);
188 // PUC contract: __gc is registered for finalization at
189 // metatable-set time, not at later mutation of the metatable.
190 self.check_finalizer_userdata(g);
191 Value::Userdata(g)
192 }
193
194 /// Convenience: [`Self::create_userdata`] + [`Self::set_global`].
195 pub fn set_userdata<T: crate::vm::LuaUserdata>(
196 &mut self,
197 name: &str,
198 value: T,
199 ) -> Result<(), LuaError> {
200 let ud = self.create_userdata(value);
201 self.set_global(name, ud)
202 }
203
204 /// Borrow the host payload of a global userdata as `&T`. Returns
205 /// `None` if the global doesn't exist, isn't a userdata, isn't a
206 /// host userdata, or holds a different type than `T`.
207 ///
208 /// Takes `&mut self` because the lookup interns the key string;
209 /// returning a borrow tied to `&mut Vm` mirrors `vm.set_global`
210 /// ergonomics.
211 pub fn userdata_borrow<T: std::any::Any + 'static>(&mut self, name: &str) -> Option<&T> {
212 let key = Value::Str(self.heap.intern(name.as_bytes()));
213 // SAFETY: Gc<T> = NonNull<T> over the single-threaded GC heap.
214 let v = unsafe { (*self.globals().as_ptr()).get(key) };
215 match v {
216 Value::Userdata(g) => {
217 // SAFETY: single-threaded GC heap; the Gc<Userdata>
218 // stays live as long as it's reachable from globals.
219 let ud = unsafe { &*g.as_ptr() };
220 ud.downcast::<T>()
221 }
222 _ => None,
223 }
224 }
225
226 // ─── Rust-side coroutine drive ───────────────────────────────
227
228 /// Create a new coroutine carrying `body` (a Lua function or
229 /// any callable Value). Returns the `Value::Coro` handle ready
230 /// to be passed to [`Self::resume_coroutine`].
231 ///
232 /// Equivalent to `coroutine.create(body)` from a Rust embedder.
233 pub fn create_coroutine(&mut self, body: Value) -> Value {
234 let co = self.new_coro(body);
235 Value::Coro(co)
236 }
237
238 /// Resume a coroutine with the given arguments. Returns the
239 /// yielded values on `yield`, the return values on the body's
240 /// terminal `return`, or an error if the body raised.
241 ///
242 /// Equivalent to `coroutine.resume(co, args...)`. Returns
243 /// `Err(LuaError)` if `co` is not a `Value::Coro`.
244 pub fn resume_coroutine(
245 &mut self,
246 co: Value,
247 args: Vec<Value>,
248 ) -> Result<Vec<Value>, LuaError> {
249 let coro = match co {
250 Value::Coro(c) => c,
251 _ => return Err(LuaError(Value::Nil)),
252 };
253 self.resume_coro(coro, args)
254 }
255
256 // ─── Rust-side debug hook ────────────────────────────────────
257
258 /// Install a Rust-side debug hook (see [`crate::vm::exec::RustDebugHook`]). The
259 /// `mask` is a bitwise OR of `HOOK_MASK_CALL` / `HOOK_MASK_RETURN`
260 /// / `HOOK_MASK_LINE` / `HOOK_MASK_COUNT` exported from
261 /// [`crate::vm::exec`]. The `count` arg sets the instruction
262 /// granularity for `Count` events (ignored unless `HOOK_MASK_COUNT`
263 /// is set).
264 ///
265 /// Passing `hook = None` clears the Rust hook; the Lua-side hook
266 /// installed via `debug.sethook` is unaffected.
267 pub fn set_rust_debug_hook(
268 &mut self,
269 hook: Option<crate::vm::exec::RustDebugHook>,
270 mask: u32,
271 count: i64,
272 ) {
273 self.hook.rust_func = hook;
274 // Update event mask flags. Other categories of the Lua hook
275 // stay as they were so a Lua-side debug.sethook + Rust hook
276 // can coexist with independent event subscriptions.
277 if hook.is_some() {
278 self.hook.call |= mask & crate::vm::exec::HOOK_MASK_CALL != 0;
279 self.hook.ret |= mask & crate::vm::exec::HOOK_MASK_RETURN != 0;
280 self.hook.line |= mask & crate::vm::exec::HOOK_MASK_LINE != 0;
281 if mask & crate::vm::exec::HOOK_MASK_COUNT != 0 {
282 self.hook.count = true;
283 self.hook.count_base = count;
284 self.hook.count_left = count;
285 }
286 }
287 }
288
289 /// Clear the Rust-side debug hook (sugar over
290 /// `set_rust_debug_hook(None, 0, 0)`).
291 pub fn clear_rust_debug_hook(&mut self) {
292 self.hook.rust_func = None;
293 }
294
295 /// Read the most recently dispatched Lua opcode, if the Vm is currently
296 /// executing inside a Lua frame. Intended for use from a Count hook
297 /// (installed via [`Self::set_rust_debug_hook`] with `HOOK_MASK_COUNT`)
298 /// to tally per-opcode distribution against a workload. Reading source
299 /// is not enough to know what a hot loop executes: a decomposition is
300 /// only actionable once a runtime counter has confirmed the per-iter
301 /// op mix it assumed.
302 ///
303 /// Returns `None` outside a Lua frame (top-level setup, while a
304 /// native callback or Cont guard is on top of the call stack, etc.).
305 /// Reads `self.frames.last() → CallFrame::Lua(f) → f.closure.proto.code[f.pc - 1]`
306 /// — the just-dispatched opcode (PC has already advanced past it).
307 pub fn current_op(&self) -> Option<crate::vm::isa::Op> {
308 let f = self.jit_last_lua_frame()?;
309 let pc = (f.pc as usize).checked_sub(1)?;
310 let inst = f.closure.proto.code.get(pc)?;
311 Some(inst.op())
312 }
313
314 /// Mutable variant of [`Self::userdata_borrow`].
315 pub fn userdata_borrow_mut<T: std::any::Any + 'static>(
316 &mut self,
317 name: &str,
318 ) -> Option<&mut T> {
319 let key = Value::Str(self.heap.intern(name.as_bytes()));
320 // SAFETY: see userdata_borrow.
321 let v = unsafe { (*self.globals().as_ptr()).get(key) };
322 match v {
323 Value::Userdata(g) => {
324 // SAFETY: see userdata_borrow; the returned &mut is
325 // exclusive within the &mut self window.
326 let ud = unsafe { &mut *g.as_ptr() };
327 ud.downcast_mut::<T>()
328 }
329 _ => None,
330 }
331 }
332}