From 004f2b91f8ab8bcb16765f05e85cc4c8b730cabd Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Tue, 10 Nov 2020 17:58:20 -0800 Subject: [PATCH] Remove old Body compiler now that List compiler is in place AST body is compiled first to a LISP-like IR via the ListCompiler, which in turn converts down to a thunk. The direct AST -> Thunk compiler is no longer needed. Signed-off-by: Alek Ratzloff --- src/compile/thunk.rs | 318 +------------------------------------------ 1 file changed, 2 insertions(+), 316 deletions(-) diff --git a/src/compile/thunk.rs b/src/compile/thunk.rs index e04801b..2784cdb 100644 --- a/src/compile/thunk.rs +++ b/src/compile/thunk.rs @@ -1,10 +1,8 @@ use crate::{ - compile::{basic_block::*, error::*, Compile}, - obj::{prelude::*, reserved::*}, - syn::{ast::*, visit::*}, + compile::basic_block::*, vm::inst::*, }; -use std::{collections::BTreeMap, mem}; +use std::mem; /// A basic block of VM code. /// @@ -223,318 +221,6 @@ impl Flatten { } } -// -// struct CompileBody -// - -/// Compiles an AST body down to a `Thunk`. -/// -/// Thunks are the basic building blocks of the IR. Thunks form a chain of decision paths that may -/// be taken, which allows an optimizer to remove dead code, detect endless loops, and so on. This -/// allows for shrinking blocks of code without having to recalculate jump addresses. -pub struct CompileBody<'c> { - compile: &'c mut Compile, -} - -impl<'c> CompileBody<'c> { - pub fn new(compile: &'c mut Compile) -> Self { - CompileBody { compile } - } - - pub fn compile(&mut self, body: &'c Body) -> Result { - let thunk = self.visit_body(body)?; - Ok(thunk) - } -} - -// -// impl Visit for CompileBody -// - -impl Visit for CompileBody<'_> { - // XXX - // Trying to "future-proof" by using Result<_> in case there's some reason that an error - // may need to be thrown in the future so I don't have to wrap every return value in Ok(_) - type Out = Result; - - fn visit_body(&mut self, body: &Body) -> Self::Out { - self.compile.collect_locals(body); - let mut thunk = Thunk::Nop; - - for stmt in body.iter() { - thunk.push_thunk(stmt.accept(self)?); - } - - Ok(thunk) - } - - fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out { - DefaultAccept::default_accept(stmt, self) - } - - fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out { - // - push rhs - // - push lhs (which handles the assignment) - let mut thunk = self.visit_expr(&assign.rhs)?; - thunk.push_thunk(self.visit_lhs_expr(&assign.lhs)?); - Ok(thunk) - } - - fn visit_return_stmt(&mut self, stmt: &ReturnStmt) -> Self::Out { - let mut thunk = if let Some(expr) = stmt.expr.as_ref() { - self.visit_expr(expr)? - } else { - Inst::PushSym(NIL_NAME.sym).into() - }; - thunk.push(Inst::Return); - - Ok(thunk) - } - - fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out { - // Do different things depending on the LHS - let mut thunk; - match &lhs_expr { - LhsExpr::SetAttr(expr) => { - // - push lhs expression (without accessor) - // - setattr (access) NOTE : rhs should already be on stack - thunk = self.visit_expr(&expr.expr)?; - let attr = global_sym(expr.access.to_string()); - thunk.push(Inst::SetAttr(attr)); - } - LhsExpr::Name(local_name) => { - let sym = global_sym(local_name.to_string()); - if let Some(local) = self.compile.lookup_local(sym) { - thunk = Inst::PopLocal(Some(local)).into(); - } else { - let global = self - .compile - .lookup_global(sym) - .expect("name expected to exist someplace(?)"); - thunk = Inst::PopGlobal(Some(global)).into(); - } - } - } - Ok(thunk) - } - - fn visit_expr(&mut self, expr: &Expr) -> Self::Out { - DefaultAccept::default_accept(expr, self) - } - - fn visit_bin_expr(&mut self, expr: &BinExpr) -> Self::Out { - // - push lhs - // - push rhs - // - call operator's function - let mut thunk = self.visit_expr(&expr.lhs)?; - thunk.push_thunk(self.visit_expr(&expr.rhs)?); - let inst = match expr.op { - BinOp::Plus => Inst::BinPlus, - BinOp::Minus => Inst::BinMinus, - BinOp::Times => Inst::BinMul, - BinOp::Div => Inst::BinDiv, - BinOp::Eq => Inst::BinEq, - BinOp::Neq => Inst::BinNeq, - BinOp::Lt => Inst::BinLt, - BinOp::Le => Inst::BinLe, - BinOp::Gt => Inst::BinGt, - BinOp::Ge => Inst::BinGe, - BinOp::And => Inst::BinAnd, - BinOp::Or => Inst::BinOr, - }; - - thunk.push(inst); - Ok(thunk) - } - - fn visit_un_expr(&mut self, expr: &UnExpr) -> Self::Out { - // - push expr - // - call operator's function - let mut thunk = self.visit_expr(&expr.expr)?; - match expr.op { - UnOp::Plus => thunk.push(Inst::UnPos), - UnOp::Minus => thunk.push(Inst::UnNeg), - } - Ok(thunk) - } - - fn visit_call_expr(&mut self, expr: &CallExpr) -> Self::Out { - // - push expr - // - push args in order - // - call function - let mut thunk = self.visit_expr(&expr.expr)?; - for arg in expr.args.iter() { - thunk.push_thunk(self.visit_expr(&arg)?); - } - thunk.push(Inst::Call(expr.args.len())); - Ok(thunk) - } - - fn visit_index_expr(&mut self, expr: &IndexExpr) -> Self::Out { - // - eval expr - // - eval index - // - index - let mut thunk = self.visit_expr(&expr.expr)?; - thunk.push_thunk(self.visit_expr(&expr.index)?); - thunk.push(Inst::Index); - Ok(thunk) - } - - fn visit_access_expr(&mut self, expr: &AccessExpr) -> Self::Out { - // - eval expr - // - getattr (expr.access) - let mut thunk = self.visit_expr(&expr.expr)?; - thunk.push_thunk(Thunk::Body(vec![Inst::GetAttr(global_sym( - expr.access.to_string(), - ))])); - Ok(thunk) - } - - fn visit_fun_expr(&mut self, expr: &FunExpr) -> Self::Out { - // TODO(fun) : need captures for functions, built dynamically (or statically?) - // - static is not possible, since captures are *created* at runtime, and there's no - // instruction that will look up just one scope level - it's either locals or globals. - // - an entire "create function" instruction is probably the best way to solve it, don't - // try to be clever, just implement it like that (since I mean, python does too...) - - // - push const - // (functions are unique const values so a new function will be created for every literal - // function defined in code) - - // This is pretty much the only place where a new scope layer gets pushed beyond the start - // of the program - self.compile.push_scope_layer(); - for param in expr.params.iter() { - let sym = global_sym(param.to_string()); - self.compile.create_local(sym); - } - - // Compile function body - let mut code = self.visit_body(&expr.body)?.flatten().to_vec(); - - // If the last instruction is not a return, or if there are no instructions, then return - // :nil value. - if !matches!(code.last(), Some(Inst::Return)) { - code.push(Inst::PushSym(NIL_NAME.sym)); - code.push(Inst::Return); - } - - // remap (Sym -> Name) to be (Name -> Sym) and make sure it's all in order. - let scope_locals: BTreeMap<_, _> = self - .compile - .pop_scope_layer() - .unwrap() - .into_iter() - .map(|(sym, name)| (name, sym)) - .collect(); - - // this should be in numeric order since: - // 1. locals are created exactly once or looked up - // 2. scope_locals is a btreemap, keyed by names, which are in order from 0..N - let locals: FunLocals = scope_locals - .into_iter() - .enumerate() - .map(|(index, (name, sym))| { - assert_eq!(index, name.index()); - sym - }) - .collect(); - - let (hdl, _fun) = - self.compile - .push_const(UserFun::new_obj(code, locals, expr.params.len())); - - // TODO(compile) : determine return value at the end of the body (preferably at parse-time) - - // oh yeah, we were compiling a function body weren't we - Ok(Inst::PushConst(hdl).into()) - } - - fn visit_if_expr(&mut self, expr: &IfExpr) -> Self::Out { - // base if condition - let mut thunk = self.visit_cond_body(&expr.cond_body)?; - { - // elif branches - let mut prev_thunk: &mut Thunk = &mut thunk; - for elif_cond_body in expr.elif.iter() { - let elif_thunk = self.visit_cond_body(elif_cond_body)?; - if let Thunk::Branch(thunk_branch) = prev_thunk { - thunk_branch.thunk_false = Box::new(elif_thunk); - prev_thunk = &mut thunk_branch.thunk_false; - } else { - unreachable!("accidentally found a non-branch thunk in elif expression") - } - } - - // el branch - if let (Some(el_body), Thunk::Branch(thunk_branch)) = (&expr.el, prev_thunk) { - thunk_branch.thunk_false = Box::new(self.visit_body(el_body)?); - //prev_thunk = &mut thunk_branch.thunk_false; - } - } - Ok(thunk) - } - - fn visit_cond_body(&mut self, cond_body: &CondBody) -> Self::Out { - let mut preamble = self.visit_expr(&cond_body.cond)?; - // Attempt to call the __bool__ function on this object which leaves a value on the stack - preamble.push_thunk(vec![ - Inst::GetAttr(BOOL_MEMBER_NAME.sym), - Inst::Call(0), - Inst::CheckTruth, - ]); - Ok(Thunk::Branch(ThunkBranch { - preamble: preamble.into(), - thunk_true: self.visit_body(&cond_body.body)?.into(), - thunk_false: Box::new(Thunk::Nop), - })) - } - - fn visit_atom(&mut self, atom: &Atom) -> Self::Out { - let thunk = match atom { - Atom::Ident(ident) => { - // Small gotcha: - // Looking up a name will either result in a local or a global lookup. If it's - // a local variable first, then it's determined as a local and that's the end - // of the story... except when we're at the top scope level, we're both "local" - // *and* global. - // - // This checks to make sure that it's both a local variable and that there's more - // than one scope layer. - let sym = global_sym(ident.to_string()); - if let (true, Some(local)) = ( - self.compile.scope().layers_len() > 1, - self.compile.lookup_local(sym), - ) { - // get local - Inst::LoadLocal(local).into() - } else { - // get or create global - // create_global only makes a new global with this symbol name if one has not - // been created yet - let global = self.compile.create_global(sym); - Inst::LoadGlobal(global).into() - } - } - Atom::Sym(sym) => { - // push symbol - Inst::PushSym(global_sym(sym.clone())).into() - } - Atom::Num(num) => { - // push const - let (hdl, _) = self.compile.const_int(*num); - Inst::PushConst(hdl).into() - } - Atom::String(s) => { - // push const - let (hdl, _) = self.compile.const_str(s); - Inst::PushConst(hdl).into() - } - }; - Ok(thunk) - } -} - // // Tests //