lucia_lang/
frame.rs

1use std::{
2    cell::RefMut,
3    cmp::Ordering,
4    fmt::{self, Debug},
5    hash::{Hash, Hasher},
6};
7
8use gc_arena::{lock::RefLock, Collect, Gc, Mutation};
9
10use crate::{
11    errors::{Error, ErrorKind},
12    objects::{AnyCallback, CallbackReturn, Closure, Function, IntoValue, Table, Value},
13    Context,
14};
15
16#[derive(Clone, Copy, Collect)]
17#[collect(no_drop)]
18pub struct Frames<'gc>(pub(crate) Gc<'gc, RefLock<FramesState<'gc>>>);
19
20impl<'gc> Debug for Frames<'gc> {
21    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
22        fmt.debug_tuple("Frames")
23            .field(&(&self.0 as *const _))
24            .finish()
25    }
26}
27
28impl<'gc> PartialEq for Frames<'gc> {
29    fn eq(&self, other: &Frames<'gc>) -> bool {
30        Gc::ptr_eq(self.0, other.0)
31    }
32}
33
34impl<'gc> Eq for Frames<'gc> {}
35
36impl<'gc> Hash for Frames<'gc> {
37    fn hash<H: Hasher>(&self, state: &mut H) {
38        self.0.as_ptr().hash(state)
39    }
40}
41
42#[derive(Debug, Clone, Copy, Collect, PartialEq, Eq, Hash)]
43#[collect[require_static]]
44pub enum FrameMode {
45    // No frames are on the thread and there are no available results, the thread can be started.
46    Stopped,
47    // Frames has an active Lua frame or is waiting for a callback or sequence to finish.
48    Normal,
49    // Frames is currently inside its own `Thread::step` function.
50    Running,
51}
52
53impl fmt::Display for FrameMode {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        write!(f, "{:?}", self)
56    }
57}
58
59impl<'gc> Frames<'gc> {
60    pub fn new(mc: &Mutation<'gc>) -> Frames<'gc> {
61        Frames(Gc::new(
62            mc,
63            RefLock::new(FramesState {
64                frames: Vec::new(),
65                return_value: Value::Null,
66            }),
67        ))
68    }
69
70    pub fn mode(self) -> FrameMode {
71        if let Ok(state) = self.0.try_borrow() {
72            state.mode()
73        } else {
74            FrameMode::Running
75        }
76    }
77
78    /// If this thread is `Stopped`, start a new function with the given arguments.
79    pub fn start(
80        self,
81        ctx: Context<'gc>,
82        function: Function<'gc>,
83        args: Vec<Value<'gc>>,
84    ) -> Result<(), Error<'gc>> {
85        let mut state = self.check_mode(&ctx, FrameMode::Stopped)?;
86        state.call_function(ctx, function, args)?;
87        Ok(())
88    }
89
90    /// If the thread is in `Normal` mode, either run the Lua VM for a while or step any callback
91    /// that we are waiting on.
92    pub fn step(self, ctx: Context<'gc>) -> Result<(), Error<'gc>> {
93        let mut state = self.check_mode(&ctx, FrameMode::Normal)?;
94
95        match state.frames.last().expect("no frame to step") {
96            Frame::Callback(..) => {
97                if let Frame::Callback(callback, args) = state.frames.pop().unwrap() {
98                    state.frames.push(Frame::Calling);
99
100                    drop(state);
101                    let return_value = callback.call(ctx, args);
102                    let mut state = self.0.borrow_mut(&ctx);
103
104                    assert!(
105                        matches!(state.frames.pop(), Some(Frame::Calling)),
106                        "thread state has changed while callback was run"
107                    );
108
109                    match return_value {
110                        Ok(CallbackReturn::Return(v)) => match state.frames.last_mut() {
111                            Some(Frame::Lua(LuciaFrame { stack, .. })) => stack.push(v),
112                            _ => panic!("frame above callback must be lua frame"),
113                        },
114                        Ok(CallbackReturn::TailCall(f, args)) => {
115                            if let Err(e) = state.call_function(ctx, f, args) {
116                                state.return_error(ctx, e);
117                            }
118                        }
119                        Err(e) => state.return_error(ctx, e),
120                    }
121                }
122            }
123            Frame::Lua { .. } => {
124                const VM_GRANULARITY: u32 = 256;
125                let mut instructions = VM_GRANULARITY;
126
127                loop {
128                    match state.run_vm(ctx, instructions) {
129                        Ok(i) => {
130                            if let Some(Frame::Lua { .. }) = state.frames.last() {
131                                instructions = i;
132                                if instructions == 0 {
133                                    break;
134                                }
135                            } else {
136                                break;
137                            }
138                        }
139                        Err(e) => {
140                            state.return_error(ctx, e);
141                            break;
142                        }
143                    }
144                }
145            }
146            _ => panic!("tried to step invalid frame type"),
147        }
148
149        Ok(())
150    }
151
152    fn check_mode<'a>(
153        &'a self,
154        mc: &Mutation<'gc>,
155        expected: FrameMode,
156    ) -> Result<RefMut<'a, FramesState<'gc>>, Error<'gc>> {
157        assert!(expected != FrameMode::Running);
158        let state = self.0.try_borrow_mut(mc).map_err(|_| {
159            Error::new(ErrorKind::BadFrameMode {
160                expected,
161                found: FrameMode::Running,
162            })
163        })?;
164
165        let found = state.mode();
166        if found != expected {
167            Err(Error::new(ErrorKind::BadFrameMode { expected, found }))
168        } else {
169            Ok(state)
170        }
171    }
172}
173
174#[derive(Collect)]
175#[collect(no_drop)]
176pub(crate) struct FramesState<'gc> {
177    pub frames: Vec<Frame<'gc>>,
178    pub return_value: Value<'gc>,
179}
180
181#[derive(Collect, Debug, Clone)]
182#[collect(no_drop)]
183pub struct LuciaFrame<'gc> {
184    pub pc: usize,
185    pub closure: Closure<'gc>,
186    pub locals: Vec<Value<'gc>>,
187    pub stack: Vec<Value<'gc>>,
188    pub catch_error: bool,
189}
190
191#[derive(Collect, Debug, Clone)]
192#[collect(no_drop)]
193pub enum Frame<'gc> {
194    // An running Lua frame.
195    Lua(LuciaFrame<'gc>),
196    // A callback that has been queued but not called yet. Arguments will be in the external stack.
197    Callback(AnyCallback<'gc>, Vec<Value<'gc>>),
198    // The thread must be unlocked during external calls to permit cross-thread upvalue handling,
199    // but this presents a danger if methods on this thread were to be recursively called at this
200    // time. This frame keeps the thread in the `Running` mode during external calls, ensuring the
201    // thread cannot be mutated.
202    Calling,
203}
204
205impl<'gc> LuciaFrame<'gc> {
206    pub(crate) fn new(
207        ctx: Context<'gc>,
208        closure: Closure<'gc>,
209        mut args: Vec<Value<'gc>>,
210    ) -> Result<Self, Error<'gc>> {
211        let function = &closure.0.function;
212        let params_num = function.params.len();
213        let mut stack = vec![Value::Null; params_num];
214        match args.len().cmp(&params_num) {
215            Ordering::Less => {
216                return Err(Error::new(ErrorKind::CallArguments {
217                    value: Some(closure),
218                    required: if function.variadic.is_none() {
219                        params_num.into()
220                    } else {
221                        (params_num, None).into()
222                    },
223                    given: args.len(),
224                }));
225            }
226            Ordering::Equal => {
227                stack[..params_num].copy_from_slice(&args[..]);
228                if function.variadic.is_some() {
229                    stack.push(Value::Table(Table::new(&ctx)));
230                }
231            }
232            Ordering::Greater => {
233                if function.variadic.is_none() {
234                    return Err(Error::new(ErrorKind::CallArguments {
235                        value: Some(closure),
236                        required: params_num.into(),
237                        given: args.len(),
238                    }));
239                } else {
240                    let t = args.split_off(params_num);
241                    stack[..params_num].copy_from_slice(&args[..]);
242                    stack.push(t.into_value(ctx));
243                }
244            }
245        }
246        Ok(LuciaFrame {
247            pc: 0,
248            closure,
249            locals: vec![Value::Null; function.local_names.len()],
250            stack,
251            catch_error: false,
252        })
253    }
254}
255
256impl<'gc> FramesState<'gc> {
257    fn mode(&self) -> FrameMode {
258        match self.frames.last() {
259            None => FrameMode::Stopped,
260            Some(frame) => match frame {
261                Frame::Lua { .. } | Frame::Callback { .. } => FrameMode::Normal,
262                Frame::Calling => FrameMode::Running,
263            },
264        }
265    }
266
267    pub(crate) fn call_function(
268        &mut self,
269        ctx: Context<'gc>,
270        function: Function<'gc>,
271        args: Vec<Value<'gc>>,
272    ) -> Result<(), Error<'gc>> {
273        self.frames.push(match function {
274            Function::Closure(closure) => Frame::Lua(LuciaFrame::new(ctx, closure, args)?),
275            Function::Callback(callback) => Frame::Callback(callback, args),
276        });
277        Ok(())
278    }
279
280    pub(crate) fn tail_call(
281        &mut self,
282        ctx: Context<'gc>,
283        function: Function<'gc>,
284        args: Vec<Value<'gc>>,
285    ) -> Result<(), Error<'gc>> {
286        *self.frames.last_mut().expect("top frame is not lua frame") = match function {
287            Function::Closure(closure) => Frame::Lua(LuciaFrame::new(ctx, closure, args)?),
288            Function::Callback(callback) => Frame::Callback(callback, args),
289        };
290        Ok(())
291    }
292
293    // Return to the upper frame with results starting at the given register index.
294    pub(crate) fn return_upper(&mut self) {
295        match self.frames.pop() {
296            Some(Frame::Lua(LuciaFrame { mut stack, .. })) => {
297                let return_value = stack.pop().expect("stack error");
298                match self.frames.last_mut() {
299                    Some(Frame::Lua(LuciaFrame { stack, .. })) => stack.push(return_value),
300                    None => self.return_value = return_value,
301                    _ => panic!("lua frame must be above a lua frame"),
302                }
303            }
304            _ => panic!("top frame is not lua frame"),
305        }
306    }
307
308    pub(crate) fn return_error(&mut self, ctx: Context<'gc>, mut e: Error<'gc>) {
309        if e.traceback.is_none() {
310            e.traceback = Some(self.traceback());
311        }
312        if e.kind.recoverable() {
313            for (c, f) in self.frames.iter_mut().rev().enumerate() {
314                if let Frame::Lua(LuciaFrame {
315                    catch_error: true,
316                    stack,
317                    ..
318                }) = f
319                {
320                    stack.push(e.into_value(ctx));
321                    self.frames.truncate(c);
322                    return;
323                }
324            }
325        }
326        self.frames.clear();
327        self.return_value = e.into_value(ctx);
328    }
329
330    pub(crate) fn traceback(&self) -> Vec<Frame<'gc>> {
331        self.frames.clone()
332    }
333}