diff --git a/src/bytecode.rs b/src/bytecode.rs index ee869b7..52a07ae 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -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), } diff --git a/src/vm.rs b/src/vm.rs index 645dc04..93df565 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -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!"), } } }