Skip to main content

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}