This commit is contained in:
nora 2022-01-08 15:17:46 +01:00
parent d715213731
commit 23728f26dd
2 changed files with 53 additions and 2 deletions

View file

@ -14,6 +14,43 @@
//! Variables offsets are calculated as `local offsets`. Local offsets are calculated relative to
//! the start of the space of the stack required by that function. The interpreter must keep track
//! of the stack start of each function, to be able to calculate these offsets.
//!
//! # Function calls
//! After the function returns, the interpreter resets its stack manually back
//! to the length before the call. This means the interpreter has to do some bookkeeping, but it has
//! to do that anyways.
//!
//! # ABI
//! Function arguments are passed on the stack and can be loaded just like local variables. They belong
//! to the stack frame of the new function and are cleaned up after returning, leaving the return value where
//! the stack frame was.
//!
//! When a call happens, the current stack offset is pushed onto the stack as a `Value::Native` and
//! the element before it is stored as the new offset.
//! Then all parameters are pushed onto the stack, from last to first.
//! Afterwards, execution of the code is started. A function always has to return, and compiler
//! inserts `return null` at the end of every function implicitly.
//!
//! If a return happens, the VM loads the current value on the stack. It then goes to the start
//! of the stack frame and saves the `Value::Native` that stores the old stack offset and loads that
//! into its stack offset. It then removes the whole stack frame from the stack, and pushes the
//! returned value.
//!
//! ```text
//! old stack offset ╮ ╭ Parameters ╮ ╭ local
//! v v v v
//! ───────┬────────────┬───────────┬──────────┬─────────╮
//! Num(6) │ Native(20) │ Num(5) │ Num(6) │ Num(5) │
//! ───────┴────────────┴───────────┴──────────┴─────────╯
//! ╰────────────────────────────────────────────────── current stack frame
//! ^ ^
//! ╰─ old local ╰╮
//! │
//! │
//! Vm │
//! Current stack offset ╯
//!
//! ```
use crate::errors::Span;
use crate::vm::Value;
@ -24,7 +61,7 @@ use debug2::Formatter;
#[derive(Debug)]
pub struct FnBlock<'bc> {
/// The bytecode of the function
pub code: Vec<'bc, Instr>,
pub code: Vec<'bc, Instr<'bc>>,
/// The sizes of the stack required by the function after the instruction at the same index. This is only used
/// during compilation to calculate local variable offsets.
pub stack_sizes: Vec<'bc, usize>,
@ -50,7 +87,7 @@ impl debug2::Debug for FnBlock<'_> {
/// A bytecode instruction. For more details on the structure of the bytecode, read the module level docs [`bytecode`](`self`)
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "pretty", derive(debug2::Debug))]
pub enum Instr {
pub enum Instr<'bc> {
/// An operation that does nothing.
Nop,
@ -86,6 +123,10 @@ pub enum Instr {
/// Same as `JmpFalse`, but unconditional
Jmp(isize),
Call(&'bc FnBlock<'bc>),
Return,
/// Shrinks the stack by `usize` elements, should always be emitted before backwards jumps
ShrinkStack(usize),
}

View file

@ -28,12 +28,20 @@ pub fn execute<'bc>(
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "pretty", derive(debug2::Debug))]
pub enum Value {
/// `null`
Null,
/// A boolean value
Bool(bool),
/// A floating point number
Num(f64),
/// An interned string
String(Symbol),
/// An array of values
Array,
/// A map from string to value
Object(Object),
/// A value that is stored by the vm for bookkeeping and should never be accessed for anythign else
Native(usize),
}
const _: () = _check_val_size();
@ -170,6 +178,7 @@ impl<'bc> Vm<'bc, '_> {
}
}
Instr::Jmp(pos) => self.pc = (self.pc as isize + pos) as usize,
Instr::Call(_) => todo!(),
Instr::ShrinkStack(size) => {
assert!(self.stack.len() >= size);
let new_len = self.stack.len() - size;
@ -223,6 +232,7 @@ impl Display for Value {
Value::String(str) => f.write_str(str.as_str()),
Value::Array => todo!(),
Value::Object(_) => todo!(),
Value::Native(_) => panic!("Called display on native value!"),
}
}
}