spore_vm/val/bytecode.rs
1use std::sync::Arc;
2
3use compact_str::CompactString;
4
5use crate::parser::span::Span;
6
7use super::{NativeFunction, Symbol, UnsafeVal};
8
9/// Contains a set of instructions for the Spore VM to evaluate.
10#[derive(Clone, Debug, Default, PartialEq)]
11pub struct ByteCode {
12 /// The name of the function.
13 pub name: CompactString,
14 /// The number of arguments for the bytecode.
15 pub arg_count: usize,
16 /// The number of space reserved for local bindings.
17 pub local_bindings: usize,
18 /// The instructions for the bytecode.
19 pub instructions: Arc<[Instruction]>,
20 /// The source code for the bytecode.
21 pub source: Option<Arc<str>>,
22 /// The span containing the instruction code from `source`.
23 pub instruction_source: Box<[Span]>,
24}
25
26impl ByteCode {
27 /// Create bytecode that calls `function` with the top `arg_count` args in the stack.
28 pub fn new_native_function_call(
29 name: &str,
30 func: NativeFunction,
31 arg_count: usize,
32 ) -> ByteCode {
33 ByteCode {
34 name: name.into(),
35 arg_count: 0,
36 local_bindings: 0,
37 instructions: Arc::new([Instruction::EvalNative { func, arg_count }]),
38 source: None,
39 instruction_source: Box::default(),
40 }
41 }
42
43 /// Iterate over all values referenced by the bytecode.
44 pub fn values(&self) -> impl '_ + Iterator<Item = UnsafeVal> {
45 self.instructions
46 .iter()
47 .flat_map(|instruction| match instruction {
48 Instruction::PushConst(v) => Some(*v),
49 Instruction::PushCurrentFunction => None,
50 Instruction::Pop(_) => None,
51 Instruction::GetArg(_) => None,
52 Instruction::BindArg(_) => None,
53 Instruction::Deref(_) => None,
54 Instruction::Define(_) => None,
55 Instruction::Eval(_) => None,
56 Instruction::EvalNative { .. } => None,
57 Instruction::JumpIf(_) => None,
58 Instruction::Jump(_) => None,
59 Instruction::Return => None,
60 })
61 }
62}
63
64/// An instruction for the VM to execute.
65#[derive(Clone, Debug, PartialEq)]
66pub enum Instruction {
67 /// Push a constant onto the stack.
68 PushConst(UnsafeVal),
69 /// Push the current function onto the stack.
70 PushCurrentFunction,
71 /// Pop the top `n` elements in the stack.
72 Pop(usize),
73 /// Get the nth argument from the start of the continuation's stack.
74 GetArg(usize),
75 /// Bind the top argument to the nth argument in the stack.
76 BindArg(usize),
77 /// Get the value of a symbol at push it onto the stack.
78 Deref(Symbol),
79 /// Pop the top value of the stack and assign it to the given symbol.
80 Define(Symbol),
81 /// Pop the top `n` values of the stack. The deepmost value should be function with the rest of
82 /// the values acting as the arguments.
83 Eval(usize),
84 /// Pop the top `n` values of the stack. The deepmost value should be function with the rest of
85 /// the values acting as the arguments.
86 EvalNative {
87 func: NativeFunction,
88 arg_count: usize,
89 },
90 /// Pop the top value of the stack. If it is `true`, then jump `n` instructions.
91 JumpIf(usize),
92 /// Jump `n` instructions.
93 Jump(usize),
94 /// Return from the current function.
95 Return,
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use std::mem::size_of;
102
103 #[test]
104 fn struct_sizes_are_small_enough() {
105 assert_eq!(size_of::<Instruction>(), 3 * size_of::<usize>());
106 }
107}