stak_vm/
vm.rs

1#[cfg(feature = "profile")]
2use crate::profiler::Profiler;
3use crate::{
4    Error, Exception, StackSlot,
5    code::{INTEGER_BASE, NUMBER_BASE, SHARE_BASE, TAG_BASE},
6    cons::{Cons, NEVER},
7    instruction::Instruction,
8    memory::Memory,
9    number::Number,
10    primitive_set::PrimitiveSet,
11    r#type::Type,
12    value::{TypedValue, Value},
13};
14#[cfg(feature = "profile")]
15use core::cell::RefCell;
16use core::fmt::{self, Display, Formatter, Write};
17use stak_util::block_on;
18use winter_maybe_async::{maybe_async, maybe_await};
19
20macro_rules! trace {
21    ($prefix:literal, $data:expr) => {
22        #[cfg(feature = "trace_instruction")]
23        std::eprintln!("{}: {}", $prefix, $data);
24    };
25}
26
27macro_rules! trace_memory {
28    ($self:expr) => {
29        #[cfg(feature = "trace_memory")]
30        std::eprintln!("{}", $self);
31    };
32}
33
34macro_rules! profile_event {
35    ($self:expr, $name:literal) => {
36        #[cfg(feature = "profile")]
37        (&$self).profile_event($name);
38    };
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42struct Arity {
43    // A count does not include a variadic argument.
44    count: usize,
45    variadic: bool,
46}
47
48/// A virtual machine.
49pub struct Vm<'a, T: PrimitiveSet> {
50    primitive_set: T,
51    memory: Memory<'a>,
52    #[cfg(feature = "profile")]
53    profiler: Option<RefCell<&'a mut dyn Profiler>>,
54}
55
56// Note that some routines look unnecessarily complicated as we need to mark all
57// volatile variables live across garbage collections.
58impl<'a, T: PrimitiveSet> Vm<'a, T> {
59    /// Creates a virtual machine.
60    pub fn new(heap: &'a mut [Value], primitive_set: T) -> Result<Self, Error> {
61        Ok(Self {
62            primitive_set,
63            memory: Memory::new(heap)?,
64            #[cfg(feature = "profile")]
65            profiler: None,
66        })
67    }
68
69    /// Sets a profiler.
70    #[cfg(feature = "profile")]
71    pub fn with_profiler(self, profiler: &'a mut dyn Profiler) -> Self {
72        Self {
73            profiler: Some(profiler.into()),
74            ..self
75        }
76    }
77
78    /// Returns a reference to a primitive set.
79    pub const fn primitive_set(&self) -> &T {
80        &self.primitive_set
81    }
82
83    /// Returns a mutable reference to a primitive set.
84    pub const fn primitive_set_mut(&mut self) -> &mut T {
85        &mut self.primitive_set
86    }
87
88    /// Runs bytecodes on a virtual machine synchronously.
89    ///
90    /// # Panics
91    ///
92    /// Panics if asynchronous operations occur during the run.
93    pub fn run_sync(&mut self) -> Result<(), T::Error> {
94        block_on!(self.run())
95    }
96
97    /// Runs bytecodes on a virtual machine.
98    #[maybe_async]
99    pub fn run(&mut self) -> Result<(), T::Error> {
100        while let Err(error) = maybe_await!(self.run_with_continuation()) {
101            if error.is_critical() {
102                return Err(error);
103            }
104
105            let Some(continuation) = self.memory.cdr(self.memory.null()).to_cons() else {
106                return Err(error);
107            };
108
109            if self.memory.cdr(continuation).tag() != Type::Procedure as _ {
110                return Err(error);
111            }
112
113            self.memory.set_register(continuation);
114            let string = self.memory.build_string("")?;
115            let symbol = self.memory.allocate(
116                self.memory.register().into(),
117                string.set_tag(Type::Symbol as _).into(),
118            )?;
119            let code = self.memory.allocate(
120                symbol.into(),
121                self.memory
122                    .code()
123                    .set_tag(
124                        Instruction::Call as u16
125                            + Self::build_arity(Arity {
126                                count: 1,
127                                variadic: false,
128                            }) as u16,
129                    )
130                    .into(),
131            )?;
132            self.memory.set_code(code);
133
134            self.memory.set_register(self.memory.null());
135            write!(&mut self.memory, "{error}").map_err(Error::from)?;
136            let code = self.memory.allocate(
137                self.memory.register().into(),
138                self.memory
139                    .code()
140                    .set_tag(Instruction::Constant as _)
141                    .into(),
142            )?;
143            self.memory.set_code(code);
144        }
145
146        Ok(())
147    }
148
149    #[maybe_async]
150    fn run_with_continuation(&mut self) -> Result<(), T::Error> {
151        while self.memory.code() != self.memory.null() {
152            let instruction = self.memory.cdr(self.memory.code()).assume_cons();
153
154            trace!("instruction", instruction.tag());
155
156            match instruction.tag() {
157                Instruction::CONSTANT => self.constant()?,
158                Instruction::GET => self.get()?,
159                Instruction::SET => self.set(),
160                Instruction::IF => self.r#if(),
161                code => maybe_await!(
162                    self.call(instruction, code as usize - Instruction::CALL as usize)
163                )?,
164            }
165
166            self.advance_code();
167
168            trace_memory!(self);
169        }
170
171        Ok(())
172    }
173
174    #[inline]
175    fn constant(&mut self) -> Result<(), Error> {
176        let constant = self.operand();
177
178        trace!("constant", constant);
179
180        self.memory.push(constant)?;
181
182        Ok(())
183    }
184
185    #[inline]
186    fn get(&mut self) -> Result<(), Error> {
187        let operand = self.operand_cons();
188        let value = self.memory.car(operand);
189
190        trace!("operand", operand);
191        trace!("value", value);
192
193        self.memory.push(value)?;
194
195        Ok(())
196    }
197
198    #[inline]
199    fn set(&mut self) {
200        let operand = self.operand_cons();
201        let value = self.memory.pop();
202
203        trace!("operand", operand);
204        trace!("value", value);
205
206        self.memory.set_car(operand, value);
207    }
208
209    #[inline]
210    fn r#if(&mut self) {
211        let cons = self.memory.stack();
212
213        if self.memory.pop() != self.memory.boolean(false).into() {
214            self.memory.set_cdr(cons, self.operand());
215            self.memory.set_code(cons);
216        }
217    }
218
219    #[inline(always)]
220    #[maybe_async]
221    fn call(&mut self, instruction: Cons, arity: usize) -> Result<(), T::Error> {
222        let r#return = instruction == self.memory.null();
223        let procedure = self.procedure();
224
225        trace!("procedure", procedure);
226        trace!("return", r#return);
227
228        if self.environment(procedure).tag() != Type::Procedure as _ {
229            return Err(Error::ProcedureExpected.into());
230        }
231
232        match self.code(procedure).to_typed() {
233            TypedValue::Cons(code) => {
234                #[cfg(feature = "profile")]
235                self.profile_call(self.memory.code(), r#return);
236
237                let arguments = Self::parse_arity(arity);
238                let parameters =
239                    Self::parse_arity(self.memory.car(code).assume_number().to_i64() as usize);
240
241                trace!("argument count", arguments.count);
242                trace!("argument variadic", arguments.variadic);
243                trace!("parameter count", parameters.count);
244                trace!("parameter variadic", parameters.variadic);
245
246                self.memory.set_register(procedure);
247
248                let mut list = if arguments.variadic {
249                    self.memory.pop().assume_cons()
250                } else {
251                    self.memory.null()
252                };
253
254                for _ in 0..arguments.count {
255                    let value = self.memory.pop();
256                    list = self.memory.cons(value, list)?;
257                }
258
259                // Use a `code` field as an escape cell for a procedure.
260                let code = self.memory.code();
261                self.memory.set_code(self.memory.register());
262                self.memory.set_register(list);
263
264                let continuation = if r#return {
265                    self.continuation()
266                } else {
267                    self.memory
268                        .allocate(code.into(), self.memory.stack().into())?
269                };
270                let stack = self.memory.allocate(
271                    continuation.into(),
272                    self.environment(self.memory.code())
273                        .set_tag(StackSlot::Frame as _)
274                        .into(),
275                )?;
276                self.memory.set_stack(stack);
277                self.memory
278                    .set_code(self.code(self.memory.code()).assume_cons());
279
280                for _ in 0..parameters.count {
281                    if self.memory.register() == self.memory.null() {
282                        return Err(Error::ArgumentCount.into());
283                    }
284
285                    self.memory.push(self.memory.car(self.memory.register()))?;
286                    self.memory
287                        .set_register(self.memory.cdr(self.memory.register()).assume_cons());
288                }
289
290                if parameters.variadic {
291                    self.memory.push(self.memory.register().into())?;
292                } else if self.memory.register() != self.memory.null() {
293                    return Err(Error::ArgumentCount.into());
294                }
295            }
296            TypedValue::Number(primitive) => {
297                if Self::parse_arity(arity).variadic {
298                    let list = self.memory.pop().assume_cons();
299                    self.memory.set_register(list);
300
301                    while self.memory.register() != self.memory.null() {
302                        self.memory.push(self.memory.car(self.memory.register()))?;
303                        self.memory
304                            .set_register(self.memory.cdr(self.memory.register()).assume_cons());
305                    }
306                }
307
308                maybe_await!(
309                    self.primitive_set
310                        .operate(&mut self.memory, primitive.to_i64() as _)
311                )?;
312            }
313        }
314
315        Ok(())
316    }
317
318    #[inline]
319    const fn parse_arity(info: usize) -> Arity {
320        Arity {
321            count: info / 2,
322            variadic: info % 2 == 1,
323        }
324    }
325
326    #[inline]
327    const fn build_arity(arity: Arity) -> usize {
328        2 * arity.count + arity.variadic as usize
329    }
330
331    #[inline]
332    fn advance_code(&mut self) {
333        let mut code = self.memory.cdr(self.memory.code()).assume_cons();
334
335        if code == self.memory.null() {
336            #[cfg(feature = "profile")]
337            self.profile_return();
338
339            let continuation = self.continuation();
340            // Keep a value at the top of a stack.
341            self.memory
342                .set_cdr(self.memory.stack(), self.memory.cdr(continuation));
343
344            code = self
345                .memory
346                .cdr(self.memory.car(continuation).assume_cons())
347                .assume_cons();
348        }
349
350        self.memory.set_code(code);
351    }
352
353    const fn operand(&self) -> Value {
354        self.memory.car(self.memory.code())
355    }
356
357    const fn operand_cons(&self) -> Cons {
358        match self.operand().to_typed() {
359            TypedValue::Cons(cons) => cons,
360            TypedValue::Number(index) => self.memory.tail(self.memory.stack(), index.to_i64() as _),
361        }
362    }
363
364    // (code . environment)
365    const fn procedure(&self) -> Cons {
366        self.memory.car(self.operand_cons()).assume_cons()
367    }
368
369    // (parameter-count . instruction-list) | primitive-id
370    const fn code(&self, procedure: Cons) -> Value {
371        self.memory.car(procedure)
372    }
373
374    const fn environment(&self, procedure: Cons) -> Cons {
375        self.memory.cdr(procedure).assume_cons()
376    }
377
378    // (code . stack)
379    const fn continuation(&self) -> Cons {
380        let mut stack = self.memory.stack();
381
382        while self.memory.cdr(stack).assume_cons().tag() != StackSlot::Frame as _ {
383            stack = self.memory.cdr(stack).assume_cons();
384        }
385
386        self.memory.car(stack).assume_cons()
387    }
388
389    // Profiling
390
391    #[cfg(feature = "profile")]
392    fn profile_call(&self, call_code: Cons, r#return: bool) {
393        if let Some(profiler) = &self.profiler {
394            profiler
395                .borrow_mut()
396                .profile_call(&self.memory, call_code, r#return);
397        }
398    }
399
400    #[cfg(feature = "profile")]
401    fn profile_return(&self) {
402        if let Some(profiler) = &self.profiler {
403            profiler.borrow_mut().profile_return(&self.memory);
404        }
405    }
406
407    #[cfg(feature = "profile")]
408    fn profile_event(&self, name: &str) {
409        if let Some(profiler) = &self.profiler {
410            profiler.borrow_mut().profile_event(name);
411        }
412    }
413
414    /// Initializes a virtual machine with bytecodes of a program.
415    pub fn initialize(&mut self, input: impl IntoIterator<Item = u8>) -> Result<(), super::Error> {
416        profile_event!(self, "initialization_start");
417        profile_event!(self, "decode_start");
418
419        let program = self.decode_ribs(&mut input.into_iter())?;
420        self.memory
421            .set_false(self.memory.car(program).assume_cons());
422        self.memory.set_code(self.memory.cdr(program).assume_cons());
423
424        profile_event!(self, "decode_end");
425
426        // Initialize an implicit top-level frame.
427        let codes = self
428            .memory
429            .cons(Number::default().into(), self.memory.null())?
430            .into();
431        let continuation = self.memory.cons(codes, self.memory.null())?.into();
432        let stack = self.memory.allocate(
433            continuation,
434            self.memory.null().set_tag(StackSlot::Frame as _).into(),
435        )?;
436        self.memory.set_stack(stack);
437        self.memory.set_register(NEVER);
438
439        profile_event!(self, "initialization_end");
440
441        Ok(())
442    }
443
444    fn decode_ribs(&mut self, input: &mut impl Iterator<Item = u8>) -> Result<Cons, Error> {
445        while let Some(head) = input.next() {
446            if head & 1 == 0 {
447                let cdr = self.memory.top();
448                let cons = self
449                    .memory
450                    .allocate(Number::from_i64((head >> 1) as _).into(), cdr)?;
451                self.memory.set_top(cons.into());
452            } else if head & 0b10 == 0 {
453                let head = head >> 2;
454
455                if head == 0 {
456                    let value = self.memory.top();
457                    let cons = self.memory.cons(value, self.memory.code())?;
458                    self.memory.set_code(cons);
459                } else {
460                    let integer = Self::decode_integer_tail(input, head - 1, SHARE_BASE)?;
461                    let index = integer >> 1;
462
463                    if index > 0 {
464                        let cons = self.memory.tail(self.memory.code(), index as usize - 1);
465                        let head = self.memory.cdr(cons).assume_cons();
466                        let tail = self.memory.cdr(head);
467                        self.memory.set_cdr(head, self.memory.code().into());
468                        self.memory.set_cdr(cons, tail);
469                        self.memory.set_code(head);
470                    }
471
472                    let value = self.memory.car(self.memory.code());
473
474                    if integer & 1 == 0 {
475                        self.memory
476                            .set_code(self.memory.cdr(self.memory.code()).assume_cons());
477                    }
478
479                    self.memory.push(value)?;
480                }
481            } else if head & 0b100 == 0 {
482                let cons = self.memory.stack();
483                let cdr = self.memory.pop();
484                let car = self.memory.top();
485                let tag = Self::decode_integer_tail(input, head >> 3, TAG_BASE)?;
486                self.memory.set_car(cons, car);
487                self.memory.set_raw_cdr(cons, cdr.set_tag(tag as _));
488                self.memory.set_top(cons.into());
489            } else {
490                self.memory.push(
491                    Self::decode_number(Self::decode_integer_tail(input, head >> 3, NUMBER_BASE)?)
492                        .into(),
493                )?;
494            }
495        }
496
497        self.memory.pop().to_cons().ok_or(Error::BytecodeEnd)
498    }
499
500    fn decode_number(integer: u128) -> Number {
501        if integer & 1 == 0 {
502            Number::from_i64((integer >> 1) as _)
503        } else if integer & 0b10 == 0 {
504            Number::from_i64(-((integer >> 2) as i64))
505        } else {
506            let integer = integer >> 2;
507            let mantissa = if integer % 2 == 0 { 1.0 } else { -1.0 } * (integer >> 12) as f64;
508            let exponent = ((integer >> 1) % (1 << 11)) as isize - 1023;
509
510            Number::from_f64(if exponent < 0 {
511                mantissa / (1u64 << exponent.abs()) as f64
512            } else {
513                mantissa * (1u64 << exponent) as f64
514            })
515        }
516    }
517
518    fn decode_integer_tail(
519        input: &mut impl Iterator<Item = u8>,
520        mut x: u8,
521        mut base: u128,
522    ) -> Result<u128, Error> {
523        let mut y = (x >> 1) as u128;
524
525        while x & 1 != 0 {
526            x = input.next().ok_or(Error::BytecodeEnd)?;
527            y += (x as u128 >> 1) * base;
528            base *= INTEGER_BASE;
529        }
530
531        Ok(y)
532    }
533}
534
535impl<T: PrimitiveSet> Display for Vm<'_, T> {
536    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
537        write!(formatter, "{}", &self.memory)
538    }
539}
540
541#[cfg(test)]
542mod tests {
543    use super::*;
544
545    struct FakePrimitiveSet {}
546
547    impl PrimitiveSet for FakePrimitiveSet {
548        type Error = Error;
549
550        #[maybe_async]
551        fn operate(
552            &mut self,
553            _memory: &mut Memory<'_>,
554            _primitive: usize,
555        ) -> Result<(), Self::Error> {
556            Ok(())
557        }
558    }
559
560    type VoidVm = Vm<'static, FakePrimitiveSet>;
561
562    #[test]
563    fn arity() {
564        for arity in [
565            Arity {
566                count: 0,
567                variadic: false,
568            },
569            Arity {
570                count: 1,
571                variadic: false,
572            },
573            Arity {
574                count: 2,
575                variadic: false,
576            },
577            Arity {
578                count: 0,
579                variadic: true,
580            },
581            Arity {
582                count: 1,
583                variadic: true,
584            },
585            Arity {
586                count: 2,
587                variadic: true,
588            },
589        ] {
590            assert_eq!(VoidVm::parse_arity(VoidVm::build_arity(arity)), arity);
591        }
592    }
593}