From e15967e24ca55ee974fda0fe7dfd739fba869eb9 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 9 Jan 2022 13:30:20 +0100 Subject: [PATCH] function things --- src/ast.rs | 2 +- src/bytecode.rs | 25 ++++++++++++---------- src/compile.rs | 54 ++++++++++++++++++++++++++++++++++++++++-------- src/vm.rs | 55 +++++++++++++++++++++++++++++++++++++++---------- test.dil | 17 +++++++++++---- 5 files changed, 117 insertions(+), 36 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index c17d4cd..f885ec7 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -7,7 +7,7 @@ use crate::errors::Span; use crate::gc::Symbol; use bumpalo::collections::Vec; -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct Ident { pub sym: Symbol, pub span: Span, diff --git a/src/bytecode.rs b/src/bytecode.rs index c45ef6d..e02034b 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -20,7 +20,7 @@ //! to the length before the call. This means the interpreter has to do some bookkeeping, but it has //! to do that anyways. //! -//! It is the compilers job to generate the correct loading of the arguments and assure that the aritiy +//! It is the compilers job to generate the correct loading of the arguments and assure that the arity //! is correct before the `Call` instruction. //! //! @@ -31,7 +31,7 @@ //! //! 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. +//! Then all parameters are pushed onto the stack, from first to last //! 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. //! @@ -41,12 +41,12 @@ //! returned value. //! //! ```text -//! old stack offset─╮ -//! ╭─Parameters─╮ │ old FnBlock index─╮ local─╮ -//! v v v v v -//! ───────┬─────────┬──────────┬─────────────┬────────────┬────────────┬─────────╮ -//! Num(6) │ Num(5) │ Num(6) │ NativeU(20) │ NativeU(4) │ NativeU(1) │ Num(5) │ -//! ───────┴─────────┴──────────┴─────────────┴────────────┴────────────┴─────────╯ +//! old stack offset─╮ +//! ╭─Parameters─╮ │ old Function─╮ local─╮ +//! v v v v v +//! ───────┬─────────┬──────────┬─────────────┬────────────┬──────────┬─────────╮ +//! Num(6) │ Num(5) │ Num(6) │ NativeU(20) │ NativeU(4) │ Function │ Num(5) │ +//! ───────┴─────────┴──────────┴─────────────┴────────────┴──────────┴─────────╯ //! ^ ╰────────────────────────────────────────────────────────────────── current stack frame //! │ ^ //! ╰─ old local ╰─old PC @@ -67,7 +67,7 @@ use debug2::Formatter; #[derive(Debug)] pub struct FnBlock<'bc> { /// The bytecode of the function - pub code: Vec<'bc, Instr<'bc>>, + pub code: Vec<'bc, Instr>, /// 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>, @@ -90,10 +90,12 @@ impl debug2::Debug for FnBlock<'_> { } } +pub type Function = usize; + /// 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<'bc> { +pub enum Instr { /// An operation that does nothing. Nop, @@ -129,7 +131,8 @@ pub enum Instr<'bc> { /// Same as `JmpFalse`, but unconditional Jmp(isize), - Call(&'bc FnBlock<'bc>), + /// Calls the function at the top of the stack, after the parameters + Call, Return, diff --git a/src/compile.rs b/src/compile.rs index 3f24b79..54d8ab2 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1,8 +1,8 @@ //! The compiler that compiles the AST down to bytecode use crate::ast::{ - Assignment, BinaryOp, BinaryOpKind, Block, Call, Declaration, ElsePart, Expr, FnDecl, Ident, - IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt, + Assignment, BinaryOp, BinaryOpKind, Block, Call, CallKind, Declaration, ElsePart, Expr, FnDecl, + Ident, IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt, }; use crate::bytecode::{FnBlock, Instr}; use crate::errors::{CompilerError, Span}; @@ -11,6 +11,7 @@ use crate::vm::Value; use crate::{HashMap, RtAlloc}; use bumpalo::collections::Vec; use bumpalo::Bump; +use std::borrow::BorrowMut; use std::cell::RefCell; use std::rc::Rc; @@ -152,8 +153,25 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { Ok(()) } - fn compile_fn_decl(&mut self, _: &FnDecl) -> CResult { - todo!() + fn compile_fn_decl(&mut self, decl: &FnDecl) -> CResult { + let block = FnBlock { + code: Vec::new_in(self.bump), + stack_sizes: Vec::new_in(self.bump), + spans: Vec::new_in(self.bump), + arity: decl.params.len().try_into().map_err(|_| { + CompilerError::new( + decl.params[u8::MAX as usize] + .span + .extend(decl.params.last().unwrap().span), + "Too many parameters".to_string(), + ) + })?, + }; + + self.blocks.push(block); + self.current_block = self.blocks.len() - 1; + + Ok(()) } fn compile_if(&mut self, if_stmt: &IfStmt) -> CResult { @@ -214,7 +232,6 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { self.compile_block(ast_block)?; self.shrink_stack(pre_loop_stack_size, span); - let jmp_offset = self.back_jmp_offset(first_stmt_idx); self.push_instr(Instr::Jmp(jmp_offset), StackChange::None, span); @@ -358,8 +375,27 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { Ok(()) } - fn compile_expr_call(&mut self, _: &Call) -> CResult { - todo!() + fn compile_expr_call(&mut self, call: &Call) -> CResult { + let params = match &call.kind { + CallKind::Fn(params) => params, + _ => todo!(), + }; + + let name = match &call.callee { + Expr::Ident(ident) => ident, + _ => todo!(), + }; + + let offset = self.env.borrow().lookup_local(name)?; + + for param in params { + self.compile_expr(param)?; + } + + self.push_instr(Instr::Load(offset), StackChange::Grow, call.span); + self.push_instr(Instr::Call, StackChange::Grow, call.span); + + Ok(()) } fn shrink_stack(&mut self, jmp_target_stack_size: usize, span: Span) { @@ -415,13 +451,13 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> { block.stack_sizes.last().copied().unwrap_or(0) } - fn change_instr(&mut self, index: usize, instr: Instr<'bc>) { + fn change_instr(&mut self, index: usize, instr: Instr) { let block = &mut self.blocks[self.current_block]; block.code[index] = instr; } /// Pushes an instruction and returns the index of the new instruction - fn push_instr(&mut self, instr: Instr<'bc>, stack_change: StackChange, span: Span) -> usize { + fn push_instr(&mut self, instr: Instr, stack_change: StackChange, span: Span) -> usize { let block = &mut self.blocks[self.current_block]; let stack_top = block.stack_sizes.last().copied().unwrap_or(0); let new_stack_top = stack_top as isize + stack_change.as_isize(); diff --git a/src/vm.rs b/src/vm.rs index afabe25..9cb302d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,4 +1,4 @@ -use crate::bytecode::{FnBlock, Instr}; +use crate::bytecode::{FnBlock, Function, Instr}; use crate::gc::{Object, RtAlloc, Symbol}; use crate::Config; use std::fmt::{Debug, Display, Formatter}; @@ -16,6 +16,8 @@ pub fn execute<'bc>( let mut vm = Vm { blocks: bytecode, current: bytecode.first().ok_or("no bytecode found")?, + current_block_index: 0, + stack_offset: 0, pc: 0, stack: Vec::with_capacity(1024 << 5), _alloc: alloc, @@ -41,20 +43,18 @@ pub enum Value { Array, /// A map from string to value Object(Object), + /// A first-class function object + Function(Function), /// A value that is stored by the vm for bookkeeping and should never be accessed for anything else NativeU(usize), } +#[cfg(target_pointer_width = "64")] +const _: [(); 24] = [(); std::mem::size_of::()]; + #[derive(Debug, Clone, Copy)] pub struct Ptr(NonNull<()>); -const _: () = _check_val_size(); -const fn _check_val_size() { - if std::mem::size_of::() != 24 { - panic!("value got bigger!"); - } -} - const TRUE: Value = Value::Bool(true); const FALSE: Value = Value::Bool(false); @@ -67,7 +67,11 @@ struct Vm<'bc, 'io> { step: bool, // -- local to the current function + /// The current function current: &'bc FnBlock<'bc>, + current_block_index: usize, + /// The offset of the first parameter of the current function + stack_offset: usize, /// Index of the instruction currently being executed pc: usize, } @@ -96,9 +100,9 @@ impl<'bc> Vm<'bc, '_> { Instr::Nop => {} Instr::Store(index) => { let val = self.stack.pop().unwrap(); - self.stack[index] = val; + self.stack[self.stack_offset + index] = val; } - Instr::Load(index) => self.stack.push(self.stack[index]), + Instr::Load(index) => self.stack.push(self.stack[self.stack_offset + index]), Instr::PushVal(value) => self.stack.push(value), Instr::Neg => { let val = self.stack.pop().unwrap(); @@ -185,7 +189,7 @@ impl<'bc> Vm<'bc, '_> { } } Instr::Jmp(pos) => self.pc = (self.pc as isize + pos) as usize, - Instr::Call(_) => todo!(), + Instr::Call => self.call()?, Instr::Return => todo!(), Instr::ShrinkStack(size) => { assert!(self.stack.len() >= size); @@ -210,6 +214,34 @@ impl<'bc> Vm<'bc, '_> { Ok(()) } + fn call(&mut self) -> VmResult { + let old_offset = self.stack_offset; + let old_idx = self.current_block_index; + let function = self.stack.pop().unwrap(); + if let Value::Function(func) = function { + let fn_block = &self.blocks[func]; + + let new_stack_frame_start = self.stack.len() - fn_block.arity as usize; + self.stack_offset = new_stack_frame_start; + + self.stack.push(Value::NativeU(old_offset)); + self.stack.push(Value::NativeU(self.pc)); + self.stack.push(Value::Function(old_idx)); + + self.current_block_index = func; + self.current = fn_block; + + self.pc = 0; + + // TODO don't be recursive like this + self.execute_function()?; + } else { + return Err("not a function"); + } + + Ok(()) + } + fn type_error(&self) -> VmError { "bad type" } @@ -240,6 +272,7 @@ impl Display for Value { Value::String(str) => f.write_str(str.as_str()), Value::Array => todo!(), Value::Object(_) => todo!(), + Value::Function(_) => f.write_str("[function]"), Value::NativeU(_) => panic!("Called display on native value!"), } } diff --git a/test.dil b/test.dil index 7a64ee2..3b98a92 100644 --- a/test.dil +++ b/test.dil @@ -1,5 +1,14 @@ -let i = 0; +let x = 5; -while i < 1000 { - i = i + 1; -} \ No newline at end of file +x(5, 6, 7); + +fn hi(a, b,c,ds,gds,fdsa,fds,fd,sf,ds,fd,fd,fd,d,fd,fd,f,df,df,d,fd +,fd,f,fd,fd,fd,fd,fd,f,a, b,c,ds,gds,fdsa,fds,fd,sf,ds,fd,fd,fd,d,fd, +fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,fd,f,a, b,c,ds,gds,fdsa,fds,fd,sf,ds, +fd,fd,fd,d,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,fd,f,a, b,c,ds,gds,fdsa, +fds,fd,sf,ds,fd,fd,fd,d,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,fd,f,a, b,c,ds, +gds,fdsa,fds,fd,sf,ds,fd,fd,fd,d,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,fd,f,df, +df,d,fd,fd,f,fd,fd,fd,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,f,df,df,d,fd,fd,f, +fd,fd,fd,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd, +fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,fd,f,df,df,d, +fd,fd,f,fd,fd,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd,fd,fd,f,df,df,d,fd,fd,f,fd,fd,fd) {} \ No newline at end of file