function things

This commit is contained in:
nora 2022-01-09 13:30:20 +01:00
parent f23662c1f9
commit e15967e24c
5 changed files with 117 additions and 36 deletions

View file

@ -7,7 +7,7 @@ use crate::errors::Span;
use crate::gc::Symbol; use crate::gc::Symbol;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Ident { pub struct Ident {
pub sym: Symbol, pub sym: Symbol,
pub span: Span, pub span: Span,

View file

@ -20,7 +20,7 @@
//! to the length before the call. This means the interpreter has to do some bookkeeping, but it has //! to the length before the call. This means the interpreter has to do some bookkeeping, but it has
//! to do that anyways. //! 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. //! 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 //! 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. //! 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 //! 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. //! inserts `return null` at the end of every function implicitly.
//! //!
@ -41,12 +41,12 @@
//! returned value. //! returned value.
//! //!
//! ```text //! ```text
//! old stack offset─╮ //! old stack offset─╮
//! ╭─Parameters─╮ │ old FnBlock index─╮ local─╮ //! ╭─Parameters─╮ │ old Function─╮ local─╮
//! v v v v v //! v v v v v
//! ───────┬─────────┬──────────┬─────────────┬────────────┬────────────┬─────────╮ //! ───────┬─────────┬──────────┬─────────────┬────────────┬──────────┬─────────╮
//! Num(6) │ Num(5) │ Num(6) │ NativeU(20) │ NativeU(4) │ NativeU(1) │ Num(5) │ //! Num(6) │ Num(5) │ Num(6) │ NativeU(20) │ NativeU(4) │ Function │ Num(5) │
//! ───────┴─────────┴──────────┴─────────────┴────────────┴────────────┴─────────╯ //! ───────┴─────────┴──────────┴─────────────┴────────────┴──────────┴─────────╯
//! ^ ╰────────────────────────────────────────────────────────────────── current stack frame //! ^ ╰────────────────────────────────────────────────────────────────── current stack frame
//! │ ^ //! │ ^
//! ╰─ old local ╰─old PC //! ╰─ old local ╰─old PC
@ -67,7 +67,7 @@ use debug2::Formatter;
#[derive(Debug)] #[derive(Debug)]
pub struct FnBlock<'bc> { pub struct FnBlock<'bc> {
/// The bytecode of the function /// 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 /// 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. /// during compilation to calculate local variable offsets.
pub stack_sizes: Vec<'bc, usize>, 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`) /// A bytecode instruction. For more details on the structure of the bytecode, read the module level docs [`bytecode`](`self`)
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "pretty", derive(debug2::Debug))] #[cfg_attr(feature = "pretty", derive(debug2::Debug))]
pub enum Instr<'bc> { pub enum Instr {
/// An operation that does nothing. /// An operation that does nothing.
Nop, Nop,
@ -129,7 +131,8 @@ pub enum Instr<'bc> {
/// Same as `JmpFalse`, but unconditional /// Same as `JmpFalse`, but unconditional
Jmp(isize), Jmp(isize),
Call(&'bc FnBlock<'bc>), /// Calls the function at the top of the stack, after the parameters
Call,
Return, Return,

View file

@ -1,8 +1,8 @@
//! The compiler that compiles the AST down to bytecode //! The compiler that compiles the AST down to bytecode
use crate::ast::{ use crate::ast::{
Assignment, BinaryOp, BinaryOpKind, Block, Call, Declaration, ElsePart, Expr, FnDecl, Ident, Assignment, BinaryOp, BinaryOpKind, Block, Call, CallKind, Declaration, ElsePart, Expr, FnDecl,
IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt, Ident, IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt,
}; };
use crate::bytecode::{FnBlock, Instr}; use crate::bytecode::{FnBlock, Instr};
use crate::errors::{CompilerError, Span}; use crate::errors::{CompilerError, Span};
@ -11,6 +11,7 @@ use crate::vm::Value;
use crate::{HashMap, RtAlloc}; use crate::{HashMap, RtAlloc};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use std::borrow::BorrowMut;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
@ -152,8 +153,25 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> {
Ok(()) Ok(())
} }
fn compile_fn_decl(&mut self, _: &FnDecl) -> CResult { fn compile_fn_decl(&mut self, decl: &FnDecl) -> CResult {
todo!() 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 { 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.compile_block(ast_block)?;
self.shrink_stack(pre_loop_stack_size, span); self.shrink_stack(pre_loop_stack_size, span);
let jmp_offset = self.back_jmp_offset(first_stmt_idx); let jmp_offset = self.back_jmp_offset(first_stmt_idx);
self.push_instr(Instr::Jmp(jmp_offset), StackChange::None, span); self.push_instr(Instr::Jmp(jmp_offset), StackChange::None, span);
@ -358,8 +375,27 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> {
Ok(()) Ok(())
} }
fn compile_expr_call(&mut self, _: &Call) -> CResult { fn compile_expr_call(&mut self, call: &Call) -> CResult {
todo!() 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) { 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) 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]; let block = &mut self.blocks[self.current_block];
block.code[index] = instr; block.code[index] = instr;
} }
/// Pushes an instruction and returns the index of the new instruction /// 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 block = &mut self.blocks[self.current_block];
let stack_top = block.stack_sizes.last().copied().unwrap_or(0); let stack_top = block.stack_sizes.last().copied().unwrap_or(0);
let new_stack_top = stack_top as isize + stack_change.as_isize(); let new_stack_top = stack_top as isize + stack_change.as_isize();

View file

@ -1,4 +1,4 @@
use crate::bytecode::{FnBlock, Instr}; use crate::bytecode::{FnBlock, Function, Instr};
use crate::gc::{Object, RtAlloc, Symbol}; use crate::gc::{Object, RtAlloc, Symbol};
use crate::Config; use crate::Config;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
@ -16,6 +16,8 @@ pub fn execute<'bc>(
let mut vm = Vm { let mut vm = Vm {
blocks: bytecode, blocks: bytecode,
current: bytecode.first().ok_or("no bytecode found")?, current: bytecode.first().ok_or("no bytecode found")?,
current_block_index: 0,
stack_offset: 0,
pc: 0, pc: 0,
stack: Vec::with_capacity(1024 << 5), stack: Vec::with_capacity(1024 << 5),
_alloc: alloc, _alloc: alloc,
@ -41,20 +43,18 @@ pub enum Value {
Array, Array,
/// A map from string to value /// A map from string to value
Object(Object), 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 /// A value that is stored by the vm for bookkeeping and should never be accessed for anything else
NativeU(usize), NativeU(usize),
} }
#[cfg(target_pointer_width = "64")]
const _: [(); 24] = [(); std::mem::size_of::<Value>()];
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Ptr(NonNull<()>); pub struct Ptr(NonNull<()>);
const _: () = _check_val_size();
const fn _check_val_size() {
if std::mem::size_of::<Value>() != 24 {
panic!("value got bigger!");
}
}
const TRUE: Value = Value::Bool(true); const TRUE: Value = Value::Bool(true);
const FALSE: Value = Value::Bool(false); const FALSE: Value = Value::Bool(false);
@ -67,7 +67,11 @@ struct Vm<'bc, 'io> {
step: bool, step: bool,
// -- local to the current function // -- local to the current function
/// The current function
current: &'bc FnBlock<'bc>, 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 /// Index of the instruction currently being executed
pc: usize, pc: usize,
} }
@ -96,9 +100,9 @@ impl<'bc> Vm<'bc, '_> {
Instr::Nop => {} Instr::Nop => {}
Instr::Store(index) => { Instr::Store(index) => {
let val = self.stack.pop().unwrap(); 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::PushVal(value) => self.stack.push(value),
Instr::Neg => { Instr::Neg => {
let val = self.stack.pop().unwrap(); 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::Jmp(pos) => self.pc = (self.pc as isize + pos) as usize,
Instr::Call(_) => todo!(), Instr::Call => self.call()?,
Instr::Return => todo!(), Instr::Return => todo!(),
Instr::ShrinkStack(size) => { Instr::ShrinkStack(size) => {
assert!(self.stack.len() >= size); assert!(self.stack.len() >= size);
@ -210,6 +214,34 @@ impl<'bc> Vm<'bc, '_> {
Ok(()) 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 { fn type_error(&self) -> VmError {
"bad type" "bad type"
} }
@ -240,6 +272,7 @@ impl Display for Value {
Value::String(str) => f.write_str(str.as_str()), Value::String(str) => f.write_str(str.as_str()),
Value::Array => todo!(), Value::Array => todo!(),
Value::Object(_) => todo!(), Value::Object(_) => todo!(),
Value::Function(_) => f.write_str("[function]"),
Value::NativeU(_) => panic!("Called display on native value!"), Value::NativeU(_) => panic!("Called display on native value!"),
} }
} }

View file

@ -1,5 +1,14 @@
let i = 0; let x = 5;
while i < 1000 { x(5, 6, 7);
i = i + 1;
} 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) {}