rigz_vm/vm/
mod.rs

1mod options;
2mod runner;
3mod values;
4
5use crate::call_frame::Frames;
6use crate::lifecycle::{Lifecycle, TestResults};
7use crate::process::{ModulesMap, Process, SpawnedProcess};
8use crate::{generate_builder, CallFrame, Instruction, Runner, Scope, VMStack, Variable};
9use crate::{handle_js, out, outln, Module, RigzBuilder, VMError, Value};
10use derive_more::IntoIterator;
11use std::cell::RefCell;
12use std::collections::HashMap;
13use std::fmt::Debug;
14use std::time::Duration;
15
16pub use options::VMOptions;
17pub use values::*;
18
19#[derive(Debug)]
20pub struct VM<'vm> {
21    pub scopes: Vec<Scope<'vm>>,
22    pub frames: Frames<'vm>,
23    pub modules: ModulesMap<'vm>,
24    pub stack: VMStack,
25    pub sp: usize,
26    pub options: VMOptions,
27    pub lifecycles: Vec<Lifecycle>,
28    pub constants: Vec<Value>,
29    pub(crate) processes: Vec<SpawnedProcess<'vm>>,
30}
31
32impl<'vm> RigzBuilder<'vm> for VM<'vm> {
33    generate_builder!();
34
35    #[inline]
36    fn build(self) -> VM<'vm> {
37        self
38    }
39}
40
41impl Default for VM<'_> {
42    #[inline]
43    fn default() -> Self {
44        Self {
45            scopes: vec![Scope::default()],
46            frames: Default::default(),
47            modules: Default::default(),
48            sp: 0,
49            options: Default::default(),
50            lifecycles: Default::default(),
51            constants: Default::default(),
52            stack: Default::default(),
53            processes: vec![],
54        }
55    }
56}
57
58impl<'vm> VM<'vm> {
59    #[inline]
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    #[inline]
65    pub fn from_scopes(scopes: Vec<Scope<'vm>>) -> Self {
66        Self {
67            scopes,
68            ..Default::default()
69        }
70    }
71
72    pub fn process_ret(&mut self, ran: bool) -> VMState {
73        match self.frames.pop() {
74            None => {
75                let source = self.next_value("process_ret - empty stack");
76                VMState::Done(source.resolve(self))
77            }
78            Some(c) => {
79                let c = c;
80                let pc = self.frames.current.borrow().pc;
81                let mut updated = false;
82                loop {
83                    let sp = self.sp;
84                    let scope = &self.scopes[sp];
85                    let len = scope.instructions.len();
86                    let propagate = len != pc && matches!(scope.named, "if" | "unless" | "else");
87                    if propagate {
88                        match self.frames.pop() {
89                            None => {
90                                let source = self.next_value("process_ret - empty stack");
91                                return VMState::Done(source.resolve(self));
92                            }
93                            Some(next) => {
94                                self.sp = next.borrow().scope_id;
95                                self.frames.current = next;
96                                updated = true;
97                            }
98                        }
99                    } else {
100                        break;
101                    }
102                }
103                if !updated {
104                    self.sp = c.borrow().scope_id;
105                    self.frames.current = c;
106                }
107                match ran {
108                    false => VMState::Running,
109                    true => {
110                        let source = self.next_value("process_ret - ran");
111                        VMState::Ran(source.resolve(self))
112                    }
113                }
114            }
115        }
116    }
117
118    #[inline]
119    fn process_instruction(&mut self, instruction: Instruction<'vm>) -> VMState {
120        match instruction {
121            Instruction::Ret => self.process_ret(false),
122            instruction => self.process_core_instruction(instruction),
123        }
124    }
125
126    fn process_instruction_scope(&mut self, instruction: Instruction<'vm>) -> VMState {
127        match instruction {
128            Instruction::Ret => self.process_ret(true),
129            ins => self.process_core_instruction(ins),
130        }
131    }
132
133    #[inline]
134    fn next_instruction(&self) -> Option<Instruction<'vm>> {
135        let scope = &self.scopes[self.sp];
136        let pc = self.frames.current.borrow().pc;
137        self.frames.current.borrow_mut().pc += 1;
138        scope.instructions.get(pc).cloned()
139    }
140
141    /// Calls run and returns an error if the resulting value is an error
142    pub fn eval(&mut self) -> Result<Value, VMError> {
143        match self.run() {
144            Value::Error(e) => Err(e),
145            v => Ok(v),
146        }
147    }
148
149    pub fn eval_within(&mut self, duration: Duration) -> Result<Value, VMError> {
150        match self.run_within(duration) {
151            Value::Error(e) => Err(e),
152            v => Ok(v),
153        }
154    }
155
156    pub fn add_bindings(&mut self, bindings: HashMap<&'vm str, (StackValue, bool)>) {
157        let mut current = self.frames.current.borrow_mut();
158        for (k, (v, mutable)) in bindings {
159            let v = if mutable {
160                Variable::Mut(v)
161            } else {
162                Variable::Let(v)
163            };
164            current.variables.insert(k, v);
165        }
166    }
167
168    /// Starts processes for each "On" lifecycle, Errors are returned as Value::Error(VMError)
169    pub fn run(&mut self) -> Value {
170        self.start_processes();
171
172        let mut run = || loop {
173            match self.step() {
174                None => {}
175                Some(v) => return v,
176            }
177        };
178
179        let res = run();
180        self.close_processes(res)
181    }
182
183    #[inline]
184    fn step(&mut self) -> Option<Value> {
185        let instruction = match self.next_instruction() {
186            // TODO this should probably be an error requiring explicit halt, result would be none
187            None => return self.stack.pop().map(|e| e.resolve(self).borrow().clone()),
188            Some(s) => s,
189        };
190
191        match self.process_instruction(instruction) {
192            VMState::Ran(v) => {
193                return Some(
194                    VMError::RuntimeError(format!("Unexpected ran state: {}", v.borrow())).into(),
195                )
196            }
197            VMState::Running => {}
198            VMState::Done(v) => return Some(v.borrow().clone()),
199        };
200        None
201    }
202
203    fn start_processes(&mut self) {
204        self.processes = self
205            .scopes
206            .iter()
207            .filter(|s| matches!(s.lifecycle, Some(Lifecycle::On(_))))
208            .map(|s| Process::spawn(s.clone(), self.options, self.modules.clone(), None))
209            .collect();
210    }
211
212    fn close_processes(&mut self, result: Value) -> Value {
213        let mut errors: Vec<VMError> = vec![];
214        for p in self.processes.drain(..) {
215            match p.close() {
216                Ok(_) => {}
217                Err(r) => {
218                    errors.push(r);
219                }
220            }
221        }
222
223        if errors.is_empty() {
224            result
225        } else {
226            let len = errors.len() - 1;
227            let messages =
228                errors
229                    .iter()
230                    .enumerate()
231                    .fold(String::new(), |mut res, (index, next)| {
232                        res.push_str(next.to_string().as_str());
233                        if index != len {
234                            res.push_str(", ");
235                        }
236                        res
237                    });
238            VMError::RuntimeError(format!("Process Failures: {messages}")).into()
239        }
240    }
241
242    pub fn run_within(&mut self, duration: Duration) -> Value {
243        #[cfg(not(feature = "js"))]
244        let now = std::time::Instant::now();
245        #[cfg(feature = "js")]
246        let now = web_time::Instant::now();
247        loop {
248            let elapsed = now.elapsed();
249            if elapsed > duration {
250                return VMError::TimeoutError(format!(
251                    "Exceeded runtime {duration:?} - {:?}",
252                    elapsed
253                ))
254                .into();
255            }
256
257            match self.step() {
258                None => {}
259                Some(v) => return v,
260            }
261        }
262    }
263
264    pub fn test(&mut self) -> TestResults {
265        // todo support parallel tests
266        let test_scopes: Vec<_> = self
267            .scopes
268            .iter()
269            .enumerate()
270            .filter_map(|(index, s)| match &s.lifecycle {
271                None => None,
272                Some(Lifecycle::Test(_)) => {
273                    let Instruction::Ret =
274                        s.instructions.last().expect("No instructions for scope")
275                    else {
276                        unreachable!("Invalid Scope")
277                    };
278                    Some((index, s.named))
279                }
280                Some(_) => None,
281            })
282            .collect();
283
284        let mut passed = 0;
285        let mut failed = 0;
286        #[cfg(not(feature = "js"))]
287        let start = std::time::Instant::now();
288        #[cfg(feature = "js")]
289        let start = web_time::Instant::now();
290        let mut failure_messages = Vec::new();
291        for (s, named) in test_scopes {
292            out!("test {named} ... ");
293            self.sp = s;
294            self.frames.current = RefCell::new(CallFrame {
295                scope_id: s,
296                ..Default::default()
297            });
298            let v = self.eval();
299            match v {
300                Err(e) => {
301                    outln!("\x1b[31mFAILED\x1b[0m");
302                    failed += 1;
303                    failure_messages.push((named.to_string(), e));
304                }
305                Ok(_) => {
306                    outln!("\x1b[32mok\x1b[0m");
307                    passed += 1;
308                }
309            };
310        }
311
312        TestResults {
313            passed,
314            failed,
315            failure_messages,
316            duration: start.elapsed(),
317        }
318    }
319
320    pub fn run_scope(&mut self) -> VMState {
321        loop {
322            let instruction = match self.next_instruction() {
323                // TODO this should probably be an error requiring explicit halt, result would be none
324                None => return VMState::Done(Value::None.into()),
325                Some(s) => s,
326            };
327
328            match self.process_instruction_scope(instruction) {
329                VMState::Running => {}
330                s => return s,
331            };
332        }
333    }
334
335    /// All variables are reset and will need to be set again by calling `add_bindings`
336    pub fn reset(&mut self) {
337        self.sp = 0;
338        self.stack.clear();
339        self.frames.reset()
340    }
341
342    /// Snapshots can't include modules or messages from in progress lifecycles
343    pub fn snapshot(&self) -> Result<Vec<u8>, VMError> {
344        let mut bytes = Vec::new();
345        bytes.push(self.options.as_byte());
346        bytes.extend((self.sp as u64).to_le_bytes());
347
348        // write registers
349        // write stack
350        // write scopes
351        // write current
352        // write call_frames
353        Ok(bytes)
354    }
355
356    /// Snapshots can't include modules so VM must be created before loading snapshot
357    pub fn load_snapshot(&mut self, bytes: Vec<u8>) -> Result<(), VMError> {
358        let mut bytes = bytes.into_iter();
359        self.options = VMOptions::from_byte(bytes.next().unwrap());
360        let mut sp = [0; 8];
361        for (i, b) in bytes.take(8).enumerate() {
362            sp[i] = b;
363        }
364        self.sp = u64::from_le_bytes(sp) as usize;
365        // load registers
366        // load stack
367        // load scopes
368        // load current
369        // load call_frames
370        Ok(())
371    }
372}
373
374#[cfg(test)]
375pub mod vm_tests {
376    use crate::builder::RigzBuilder;
377    use crate::vm::VM;
378    use crate::{VMBuilder, Value};
379    use wasm_bindgen_test::*;
380
381    #[wasm_bindgen_test(unsupported = test)]
382    fn snapshot() {
383        let mut builder = VMBuilder::new();
384        builder.add_load_instruction(Value::Bool(true).into());
385        let vm = builder.build();
386        let bytes = vm.snapshot().expect("snapshot failed");
387        let mut vm2 = VM::default();
388        vm2.load_snapshot(bytes).expect("load failed");
389        assert_eq!(vm2.options, vm.options);
390        assert_eq!(vm2.sp, vm.sp);
391        // assert_eq!(vm2.get_register(1), Value::Bool(true).into());
392    }
393}