test out better debugging

This commit is contained in:
nora 2022-04-23 17:03:17 +02:00
parent 6a8eb89381
commit 5f9ca90fd5
13 changed files with 422 additions and 145 deletions

View file

@ -5,9 +5,9 @@
use crate::errors::Span;
use crate::gc::Symbol;
use bumpalo::collections::Vec;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Ident {
pub sym: Symbol,
pub span: Span,
@ -16,12 +16,14 @@ pub struct Ident {
pub type Program<'ast> = Block<'ast>;
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Block<'ast> {
pub stmts: Vec<'ast, Stmt<'ast>>,
pub stmts: &'ast [Stmt<'ast>],
pub span: Span,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum Stmt<'ast> {
Declaration(Declaration<'ast>),
Assignment(Assignment<'ast>),
@ -37,6 +39,7 @@ pub enum Stmt<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Declaration<'ast> {
pub span: Span,
pub name: Ident,
@ -44,6 +47,7 @@ pub struct Declaration<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Assignment<'ast> {
pub span: Span,
pub lhs: Expr<'ast>,
@ -51,14 +55,16 @@ pub struct Assignment<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct FnDecl<'ast> {
pub span: Span,
pub name: Ident,
pub params: Vec<'ast, Ident>,
pub params: &'ast [Ident],
pub body: Block<'ast>,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct IfStmt<'ast> {
pub span: Span,
pub cond: Expr<'ast>,
@ -67,6 +73,7 @@ pub struct IfStmt<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum ElsePart<'ast> {
Else(Block<'ast>, Span),
ElseIf(IfStmt<'ast>, Span),
@ -81,6 +88,7 @@ impl ElsePart<'_> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct WhileStmt<'ast> {
pub span: Span,
pub cond: Expr<'ast>,
@ -88,6 +96,7 @@ pub struct WhileStmt<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum Expr<'ast> {
Ident(Ident),
Literal(Literal<'ast>),
@ -109,10 +118,11 @@ impl Expr<'_> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum Literal<'ast> {
String(Symbol, Span),
Number(f64, Span),
Array(Vec<'ast, Expr<'ast>>, Span),
Array(&'ast [Expr<'ast>], Span),
Object(Span),
Boolean(bool, Span),
Null(Span),
@ -132,6 +142,7 @@ impl Literal<'_> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct UnaryOp<'ast> {
pub span: Span,
pub expr: Expr<'ast>,
@ -139,12 +150,14 @@ pub struct UnaryOp<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum UnaryOpKind {
Not,
Neg,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct BinaryOp<'ast> {
pub span: Span,
pub lhs: Expr<'ast>,
@ -153,6 +166,7 @@ pub struct BinaryOp<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum BinaryOpKind {
And,
Or,
@ -170,6 +184,7 @@ pub enum BinaryOpKind {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Call<'ast> {
pub callee: Expr<'ast>,
pub span: Span,
@ -177,7 +192,8 @@ pub struct Call<'ast> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum CallKind<'ast> {
Field(Ident),
Fn(Vec<'ast, Expr<'ast>>),
Fn(&'ast [Expr<'ast>]),
}

View file

@ -61,14 +61,15 @@
use crate::errors::Span;
use crate::vm::Value;
use bumpalo::collections::Vec;
use std::fmt::{Debug, Formatter};
/// This struct contains all data for a function.
#[derive(Debug)]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct FnBlock<'bc> {
/// The bytecode of the function
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.
/// 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>,
/// The corresponding source code location of each instruction. This is debuginfo and only
/// used if there are errors.
@ -78,23 +79,19 @@ pub struct FnBlock<'bc> {
pub arity: u32,
}
#[cfg(feature = "_debug")]
impl debug2::Debug for FnBlock<'_> {
fn fmt(&self, f: &mut debug2::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FnBlock")
.field("code", &self.code.as_slice())
.field("stack_sizes", &self.stack_sizes.as_slice())
.field("spans", &self.spans.as_slice())
.field("arity", &self.arity)
.finish()
impl Debug for FnBlock<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.code.fmt(f)
}
}
/// Index into the block list
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)]
#[cfg_attr(feature = "_debug", derive(debug2::Debug))]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum Instr {
/// An operation that does nothing.
Nop,

View file

@ -471,7 +471,7 @@ impl<'bc, 'gc> Compiler<'bc, 'gc> {
let offset = self.env.borrow().lookup_local(name)?;
for param in params {
for param in params.iter() {
self.compile_expr(param)?;
}

View file

@ -13,7 +13,7 @@ pub use span::Span;
mod span {
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
#[cfg_attr(feature = "_debug", derive(debug2::Debug))]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Span {
pub start: usize,
pub end: usize,

View file

@ -18,6 +18,13 @@ pub struct Gc<T: ?Sized> {
ptr: NonNull<T>,
}
#[cfg(feature = "_debug")]
impl<T: ?Sized> dbg_pls::DebugPls for Gc<T> {
fn fmt(&self, f: dbg_pls::Formatter<'_>) {
todo!()
}
}
impl<T: ?Sized> Deref for Gc<T> {
type Target = T;
@ -30,21 +37,6 @@ impl<T: ?Sized> Deref for Gc<T> {
}
}
#[cfg(feature = "_debug")]
impl<T: debug2::Debug> debug2::Debug for Gc<T> {
fn fmt(&self, f: &mut debug2::Formatter<'_>) -> std::fmt::Result {
T::fmt(&*self, f)
}
}
#[cfg(feature = "_debug")]
impl debug2::Debug for Gc<str> {
fn fmt(&self, f: &mut debug2::Formatter<'_>) -> std::fmt::Result {
let str = self.deref();
debug2::Debug::fmt(&str, f)
}
}
impl<T: Debug + ?Sized> Debug for Gc<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
T::fmt(self, f)
@ -61,7 +53,7 @@ impl<T: ?Sized> Copy for Gc<T> {}
/// An reference to an interned String. Hashing and Equality are O(1) and just look at the pointer address
#[derive(Clone, Copy)]
#[cfg_attr(feature = "_debug", derive(debug2::Debug))]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Symbol {
gc: Gc<str>,
}
@ -74,20 +66,18 @@ type ObjectMap = HashMap<Symbol, Value>;
/// ```
/// This is inside the local x now.
#[derive(Clone, Copy)]
#[cfg_attr(feature = "_debug", derive(debug2::Debug))]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub struct Object {
gc: Gc<HeapObject>,
}
#[derive(Debug)]
#[repr(C)]
#[cfg_attr(feature = "_debug", derive(debug2::Debug))]
struct HeapObject {
kind: HeapObjectKind,
}
#[derive(Debug)]
#[cfg_attr(feature = "_debug", derive(debug2::Debug))]
enum HeapObjectKind {
String(Gc<str>),
Object(ObjectMap),

View file

@ -19,14 +19,14 @@ pub use lex::*;
pub use parse::*;
#[cfg(not(feature = "fxhash"))]
#[allow(clippy::disallowed_type)]
#[allow(clippy::disallowed_types)]
type HashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(feature = "fxhash")]
type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;
#[cfg(not(feature = "fxhash"))]
#[allow(clippy::disallowed_type)]
#[allow(clippy::disallowed_types)]
type HashSet<T> = std::collections::HashSet<T>;
#[cfg(feature = "fxhash")]
@ -59,7 +59,7 @@ pub fn run_program(program: &str, cfg: &mut Config) {
fn process_ast(program: &str, ast: &Program, mut runtime: RtAlloc, cfg: &mut Config<'_>) {
if cfg.debug {
println!("AST:\n{:?}\n", ast);
util::dbg("AST:\n", ast);
}
let bytecode_alloc = Bump::new();
@ -69,14 +69,7 @@ fn process_ast(program: &str, ast: &Program, mut runtime: RtAlloc, cfg: &mut Con
match bytecode {
Ok(code) => {
if cfg.debug {
#[cfg(feature = "_debug")]
{
println!("Bytecode:\n{}\n", debug2::pprint(code));
}
#[cfg(not(feature = "_debug"))]
{
println!("Bytecode:\n{:#?}\n", code);
}
println!("Bytecode:\n{:#?}\n", code);
}
let result = vm::execute(code, runtime, cfg);

View file

@ -97,12 +97,12 @@ where
}
}
fn statement_list(&mut self) -> ParseResult<Vec<'ast, Stmt<'ast>>> {
fn statement_list(&mut self) -> ParseResult<&'ast [Stmt<'ast>]> {
enter_parse!(self);
let mut stmts = Vec::new_in(self.bump);
let return_stmts = loop {
if let Some(TokenKind::BraceC) | None = self.peek_kind() {
break Ok(stmts);
break Ok(stmts.into_bump_slice());
}
let stmt = self.statement()?;
stmts.push(stmt);
@ -190,7 +190,7 @@ where
}))
}
fn fn_args(&mut self) -> ParseResult<Vec<'ast, Ident>> {
fn fn_args(&mut self) -> ParseResult<&'ast [Ident]> {
enter_parse!(self);
self.expect(TokenKind::ParenO)?;
@ -597,7 +597,7 @@ where
return_expr
}
fn parse_list<T, F>(&mut self, close: TokenKind, mut parser: F) -> ParseResult<Vec<'ast, T>>
fn parse_list<T, F>(&mut self, close: TokenKind, mut parser: F) -> ParseResult<&'ast [T]>
where
F: FnMut(&mut Self) -> ParseResult<T>,
{
@ -606,7 +606,7 @@ where
let mut elements = Vec::new_in(self.bump);
if self.peek_kind() == Some(&close) {
return Ok(elements);
return Ok(elements.into_bump_slice());
}
let expr = parser(self)?;
@ -632,7 +632,7 @@ where
}
exit_parse!(self);
Ok(elements)
Ok(elements.into_bump_slice())
}
// token helpers

View file

@ -1,9 +1,20 @@
/// Statically asserts that the size of the type is x bytes big (on 64-bit)
macro_rules! assert_size {
($name:ident == $size:expr) => {
($name:ident <= $size:expr) => {
#[cfg(target_pointer_width = "64")]
const _: [(); $size] = [(); ::std::mem::size_of::<$name>()];
};
}
pub(crate) use assert_size;
use std::fmt::Display;
#[cfg(feature = "_debug")]
pub fn dbg(prefix: impl Display, x: impl dbg_pls::DebugPls) {
eprintln!("{prefix}{}", dbg_pls::pretty(&x))
}
#[cfg(not(feature = "_debug"))]
pub fn dbg(prefix: impl Display, x: impl std::fmt::Debug) {
eprintln!("{prefix}{x:#?}");
}

View file

@ -8,7 +8,8 @@ use std::io::{Read, Write};
type VmError = Box<&'static str>;
type VmResult = Result<(), VmError>;
util::assert_size!(VmResult == 8);
// never get bigger than a machine word.
util::assert_size!(VmResult <= std::mem::size_of::<usize>());
pub fn execute<'bc>(
bytecode: &'bc [FnBlock<'bc>],
@ -31,7 +32,7 @@ pub fn execute<'bc>(
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "_debug", derive(debug2::Debug))]
#[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))]
pub enum Value {
/// `null`
Null,
@ -51,7 +52,7 @@ pub enum Value {
NativeU(usize),
}
util::assert_size!(Value == 24);
util::assert_size!(Value <= 24);
const TRUE: Value = Value::Bool(true);
const FALSE: Value = Value::Bool(false);
@ -70,22 +71,23 @@ struct Vm<'bc, 'io> {
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 next instruction being executed. is out of bounds if the current
/// instruction is the last one
pc: usize,
}
impl<'bc> Vm<'bc, '_> {
fn execute_function(&mut self) -> VmResult {
let code = &self.current.code;
loop {
let instr = code.get(self.pc);
let instr = self.current.code.get(self.pc);
self.pc += 1;
match instr {
Some(&instr) => self.dispatch_instr(instr)?,
None => return Ok(()),
}
// debug_assert_eq!(self.current.stack_sizes[self.pc], self.stack.len());
self.pc += 1;
if self.pc > 0 {
debug_assert_eq!(self.current.stack_sizes[self.pc - 1], self.stack.len());
}
}
}
@ -188,8 +190,7 @@ impl<'bc> Vm<'bc, '_> {
}
Instr::Jmp(pos) => self.pc = (self.pc as isize + pos) as usize,
Instr::Call => self.call()?,
// todo implement
Instr::Return => self.ret()?,
Instr::Return => return Ok(()),
Instr::ShrinkStack(size) => {
assert!(self.stack.len() >= size);
let new_len = self.stack.len() - size;
@ -217,26 +218,22 @@ impl<'bc> Vm<'bc, '_> {
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 function = function.unwrap_function();
let fn_block = &self.blocks[function];
let new_stack_frame_start = self.stack.len();
self.stack_offset = new_stack_frame_start;
let new_stack_frame_start = self.stack.len();
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.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.current_block_index = function;
self.current = fn_block;
self.pc = 0;
self.pc = 0;
// TODO don't be recursive like this
self.execute_function()?;
} else {
return Err("not a function".into());
}
// we are now set up correctly, let the next instruction run
Ok(())
}
@ -246,9 +243,9 @@ impl<'bc> Vm<'bc, '_> {
let bookkeeping_offset = self.stack_offset + current_arity;
let old_stack_offset = self.stack[bookkeeping_offset].as_native_int();
let old_pc = self.stack[bookkeeping_offset + 1].as_native_int();
let old_function = self.stack[bookkeeping_offset + 2].as_function();
let old_stack_offset = self.stack[bookkeeping_offset].unwrap_native_int();
let old_pc = self.stack[bookkeeping_offset + 1].unwrap_native_int();
let old_function = self.stack[bookkeeping_offset + 2].unwrap_function();
self.stack_offset = old_stack_offset;
self.pc = old_pc;
@ -281,7 +278,7 @@ Expected Stack size after instruction: {}",
impl Value {
/// Unwrap the Value into a `usize` expecting the `NativeU` variant
fn as_native_int(&self) -> usize {
fn unwrap_native_int(&self) -> usize {
if let Value::NativeU(n) = self {
*n
} else {
@ -290,7 +287,7 @@ impl Value {
}
/// Unwrap the Value into a `Function` expecting the `Function` variant
fn as_function(&self) -> Function {
fn unwrap_function(&self) -> Function {
if let Value::Function(fun) = self {
*fun
} else {