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