luna_core/runtime/coroutine.rs
1//! Coroutine (thread) objects (P05). A coroutine owns a full execution context
2//! — value stack, call frames, open upvalues, to-be-closed slots and stack top
3//! — that is swapped into the running `Vm` while it is active and saved back
4//! here while it is suspended.
5
6use crate::runtime::Upvalue;
7use crate::runtime::function::{CallFrame, ContKind};
8use crate::runtime::heap::{Gc, GcHeader, Marker};
9use crate::runtime::table::Table;
10use crate::runtime::value::Value;
11
12/// Lua coroutine status (PUC `coroutine.status`).
13#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub enum CoroStatus {
15 /// created or yielded — resumable
16 Suspended,
17 /// currently executing (the running thread)
18 Running,
19 /// resumed another coroutine and is waiting for it
20 Normal,
21 /// finished or errored — not resumable
22 Dead,
23}
24
25/// A Lua coroutine (`thread`) — one independent execution context plus its
26/// saved value/frame stacks and resume linkage.
27#[repr(C)]
28pub struct Coro {
29 pub(crate) hdr: GcHeader,
30 /// Resume state (suspended / running / normal / dead).
31 pub status: CoroStatus,
32 /// the body function, kept for the first resume (and as a GC root)
33 pub body: Value,
34 /// whether the body frame has been pushed yet (first resume vs. continue)
35 pub started: bool,
36 /// the coroutine that resumed this one (to restore on yield/return and for
37 /// `coroutine.running`); `None` once suspended/dead
38 pub resumer: Option<Gc<Coro>>,
39 /// where execution suspended on `yield`: the call slot and result count to
40 /// finish that call with the next resume's arguments
41 pub resume_at: Option<(u32, i32)>,
42 /// the error object a coroutine died with (when it errored rather than
43 /// returned); `coroutine.close` reports it once, then clears it
44 pub error_value: Option<Value>,
45 /// snapshot of the traceback at the error point — captured before the
46 /// dying coroutine's frames are unwound, so `debug.traceback(co)` on a
47 /// dead-with-error coroutine still shows the error site (PUC's
48 /// `luaG_errormsg` flow plus a per-thread `errfunc` snapshot).
49 pub error_traceback: Option<Vec<u8>>,
50 // ---- saved execution context (valid while suspended/normal) ----
51 /// Saved value stack.
52 pub stack: Vec<Value>,
53 /// Saved frame stack (Lua frames + native continuations).
54 pub frames: Vec<CallFrame>,
55 /// Open-upvalue list — `(stack slot, upvalue cell)` pairs.
56 pub open_upvals: Vec<(u32, Gc<Upvalue>)>,
57 /// Stack indices of registered `<close>` slots (5.4+).
58 pub tbc: Vec<u32>,
59 /// Saved stack top.
60 pub top: u32,
61 /// live pcall/xpcall continuation count (PUC nCcalls portion); see Vm
62 pub pcall_depth: u32,
63 /// this thread's debug hook state (PUC per-thread hook/hookmask)
64 pub hook: crate::vm::exec::HookState,
65 /// PUC `L->l_gt` — the thread's own globals table. Captured from the
66 /// resuming thread at create time, then swapped with `Vm.globals` on
67 /// every resume/yield boundary so a `setfenv(0, env)` inside the
68 /// coroutine only retunes *this* thread (5.1 closure.lua :177 pins
69 /// this — yielding `getfenv()` after the rewire must see the
70 /// coroutine's own per-closure env, not the caller's).
71 pub globals: Gc<Table>,
72}
73
74impl Coro {
75 pub(crate) fn trace(&self, m: &mut Marker) {
76 m.value(self.body);
77 for &v in self.stack.iter() {
78 m.value(v);
79 }
80 for cf in self.frames.iter() {
81 match cf {
82 CallFrame::Lua(f) => {
83 m.header(f.closure.as_ptr() as *mut GcHeader);
84 }
85 CallFrame::Cont(nc) => {
86 if let ContKind::Xpcall { handler } = nc.kind {
87 m.value(handler);
88 }
89 }
90 }
91 }
92 for &(_, uv) in self.open_upvals.iter() {
93 m.header(uv.as_ptr() as *mut GcHeader);
94 }
95 if let Some(r) = self.resumer {
96 m.header(r.as_ptr() as *mut GcHeader);
97 }
98 if let Some(e) = self.error_value {
99 m.value(e);
100 }
101 if let Some(h) = self.hook.func {
102 m.value(h);
103 }
104 m.value(Value::Table(self.globals));
105 }
106}