diff options
author | Griffin Smith <root@gws.fyi> | 2021-03-07T20·29-0500 |
---|---|---|
committer | Griffin Smith <root@gws.fyi> | 2021-03-07T20·29-0500 |
commit | 80f8ede0bbc9799d5199707e1e1ad8e80e4ca7ac (patch) | |
tree | cbb418b042583714fe09f946f1b9a03d1d98857f /src/codegen/llvm.rs |
Initial commit
Diffstat (limited to 'src/codegen/llvm.rs')
-rw-r--r-- | src/codegen/llvm.rs | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/src/codegen/llvm.rs b/src/codegen/llvm.rs new file mode 100644 index 000000000000..ff92c3373176 --- /dev/null +++ b/src/codegen/llvm.rs @@ -0,0 +1,282 @@ +use std::path::Path; +use std::result; + +use inkwell::basic_block::BasicBlock; +use inkwell::builder::Builder; +pub use inkwell::context::Context; +use inkwell::module::Module; +use inkwell::support::LLVMString; +use inkwell::types::FunctionType; +use inkwell::values::{BasicValueEnum, FunctionValue}; +use inkwell::IntPredicate; +use thiserror::Error; + +use crate::ast::{BinaryOperator, Decl, Expr, Fun, Ident, Literal, UnaryOperator}; +use crate::common::env::Env; + +#[derive(Debug, PartialEq, Eq, Error)] +pub enum Error { + #[error("Undefined variable {0}")] + UndefinedVariable(Ident<'static>), + + #[error("LLVM Error: {0}")] + LLVMError(String), +} + +impl From<LLVMString> for Error { + fn from(s: LLVMString) -> Self { + Self::LLVMError(s.to_string()) + } +} + +pub type Result<T> = result::Result<T, Error>; + +pub struct Codegen<'ctx, 'ast> { + context: &'ctx Context, + pub module: Module<'ctx>, + builder: Builder<'ctx>, + env: Env<'ast, BasicValueEnum<'ctx>>, + function: Option<FunctionValue<'ctx>>, +} + +impl<'ctx, 'ast> Codegen<'ctx, 'ast> { + pub fn new(context: &'ctx Context, module_name: &str) -> Self { + let module = context.create_module(module_name); + let builder = context.create_builder(); + Self { + context, + module, + builder, + env: Default::default(), + function: None, + } + } + + pub fn new_function<'a>( + &'a mut self, + name: &str, + ty: FunctionType<'ctx>, + ) -> &'a FunctionValue<'ctx> { + self.function = Some(self.module.add_function(name, ty, None)); + let basic_block = self.append_basic_block("entry"); + self.builder.position_at_end(basic_block); + self.function.as_ref().unwrap() + } + + pub fn finish_function(&self, res: &BasicValueEnum<'ctx>) { + self.builder.build_return(Some(res)); + } + + pub fn append_basic_block(&self, name: &str) -> BasicBlock<'ctx> { + self.context + .append_basic_block(self.function.unwrap(), name) + } + + pub fn codegen_expr(&mut self, expr: &'ast Expr<'ast>) -> Result<BasicValueEnum<'ctx>> { + match expr { + Expr::Ident(id) => self + .env + .resolve(id) + .cloned() + .ok_or_else(|| Error::UndefinedVariable(id.to_owned())), + Expr::Literal(Literal::Int(i)) => { + let ty = self.context.i64_type(); + Ok(BasicValueEnum::IntValue(ty.const_int(*i, false))) + } + Expr::UnaryOp { op, rhs } => { + let rhs = self.codegen_expr(rhs)?; + match op { + UnaryOperator::Not => unimplemented!(), + UnaryOperator::Neg => Ok(BasicValueEnum::IntValue( + self.builder.build_int_neg(rhs.into_int_value(), "neg"), + )), + } + } + Expr::BinaryOp { lhs, op, rhs } => { + let lhs = self.codegen_expr(lhs)?; + let rhs = self.codegen_expr(rhs)?; + match op { + BinaryOperator::Add => { + Ok(BasicValueEnum::IntValue(self.builder.build_int_add( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + ))) + } + BinaryOperator::Sub => { + Ok(BasicValueEnum::IntValue(self.builder.build_int_sub( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + ))) + } + BinaryOperator::Mul => { + Ok(BasicValueEnum::IntValue(self.builder.build_int_sub( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + ))) + } + BinaryOperator::Div => { + Ok(BasicValueEnum::IntValue(self.builder.build_int_signed_div( + lhs.into_int_value(), + rhs.into_int_value(), + "add", + ))) + } + BinaryOperator::Pow => unimplemented!(), + BinaryOperator::Equ => { + Ok(BasicValueEnum::IntValue(self.builder.build_int_compare( + IntPredicate::EQ, + lhs.into_int_value(), + rhs.into_int_value(), + "eq", + ))) + } + BinaryOperator::Neq => todo!(), + } + } + Expr::Let { bindings, body } => { + self.env.push(); + for (id, val) in bindings { + let val = self.codegen_expr(val)?; + self.env.set(id, val); + } + let res = self.codegen_expr(body); + self.env.pop(); + res + } + Expr::If { + condition, + then, + else_, + } => { + let then_block = self.append_basic_block("then"); + let else_block = self.append_basic_block("else"); + let join_block = self.append_basic_block("join"); + let condition = self.codegen_expr(condition)?; + self.builder.build_conditional_branch( + condition.into_int_value(), + then_block, + else_block, + ); + self.builder.position_at_end(then_block); + let then_res = self.codegen_expr(then)?; + self.builder.build_unconditional_branch(join_block); + + self.builder.position_at_end(else_block); + let else_res = self.codegen_expr(else_)?; + self.builder.build_unconditional_branch(join_block); + + self.builder.position_at_end(join_block); + let phi = self.builder.build_phi(self.context.i64_type(), "join"); + phi.add_incoming(&[(&then_res, then_block), (&else_res, else_block)]); + Ok(phi.as_basic_value()) + } + } + } + + pub fn codegen_decl(&mut self, decl: &'ast Decl<'ast>) -> Result<()> { + match decl { + Decl::Fun(Fun { name, args, body }) => { + let i64_type = self.context.i64_type(); + self.new_function( + name.into(), + i64_type.fn_type( + args.iter() + .map(|_| i64_type.into()) + .collect::<Vec<_>>() + .as_slice(), + false, + ), + ); + self.env.push(); + for (i, arg) in args.iter().enumerate() { + self.env + .set(arg, self.function.unwrap().get_nth_param(i as u32).unwrap()); + } + let res = self.codegen_expr(body)?; + self.env.pop(); + self.finish_function(&res); + Ok(()) + } + } + } + + pub fn codegen_main(&mut self, expr: &'ast Expr<'ast>) -> Result<()> { + self.new_function("main", self.context.i64_type().fn_type(&[], false)); + let res = self.codegen_expr(expr)?; + self.finish_function(&res); + Ok(()) + } + + pub fn print_to_file<P>(&self, path: P) -> Result<()> + where + P: AsRef<Path>, + { + Ok(self.module.print_to_file(path)?) + } + + pub fn binary_to_file<P>(&self, path: P) -> Result<()> + where + P: AsRef<Path>, + { + if self.module.write_bitcode_to_path(path.as_ref()) { + Ok(()) + } else { + Err(Error::LLVMError( + "Error writing bitcode to output path".to_owned(), + )) + } + } +} + +#[cfg(test)] +mod tests { + use inkwell::execution_engine::JitFunction; + use inkwell::OptimizationLevel; + + use super::*; + + fn jit_eval<T>(expr: &str) -> anyhow::Result<T> { + let expr = crate::parser::expr(expr).unwrap().1; + + let context = Context::create(); + let mut codegen = Codegen::new(&context, "test"); + let execution_engine = codegen + .module + .create_jit_execution_engine(OptimizationLevel::None) + .unwrap(); + + codegen.new_function("test", context.i64_type().fn_type(&[], false)); + let res = codegen.codegen_expr(&expr)?; + codegen.finish_function(&res); + + unsafe { + let fun: JitFunction<unsafe extern "C" fn() -> T> = + execution_engine.get_function("test")?; + Ok(fun.call()) + } + } + + #[test] + fn add_literals() { + assert_eq!(jit_eval::<i64>("1 + 2").unwrap(), 3); + } + + #[test] + fn variable_shadowing() { + assert_eq!( + jit_eval::<i64>("let x = 1 in (let x = 2 in x) + x").unwrap(), + 3 + ); + } + + #[test] + fn eq() { + assert_eq!( + jit_eval::<i64>("let x = 1 in if x == 1 then 2 else 4").unwrap(), + 2 + ); + } +} |