aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/codegen.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen.rs')
-rw-r--r--src/codegen.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/src/codegen.rs b/src/codegen.rs
new file mode 100644
index 0000000..866aab7
--- /dev/null
+++ b/src/codegen.rs
@@ -0,0 +1,135 @@
+use std::collections::HashMap;
+
+use crate::llvm::{Builder, FnValue, Module, Value};
+use crate::parser::{ExprAST, FunctionAST, PrototypeAST};
+use crate::Either;
+
+type CodegenResult<T> = Result<T, String>;
+
+/// Code generator from kaleidoscope AST to LLVM IR.
+pub struct Codegen<'llvm, 'a> {
+ module: &'llvm Module,
+ builder: &'a Builder<'llvm>,
+}
+
+impl<'llvm, 'a> Codegen<'llvm, 'a> {
+ /// Compile either a [`PrototypeAST`] or a [`FunctionAST`] into the LLVM `module`.
+ pub fn compile(
+ module: &'llvm Module,
+ compilee: Either<&PrototypeAST, &FunctionAST>,
+ ) -> CodegenResult<FnValue<'llvm>> {
+ let cg = Codegen {
+ module,
+ builder: &Builder::with_ctx(module),
+ };
+ let mut variables = HashMap::new();
+
+ match compilee {
+ Either::A(proto) => Ok(cg.codegen_prototype(proto)),
+ Either::B(func) => cg.codegen_function(func, &mut variables),
+ }
+ }
+
+ fn codegen_expr(
+ &self,
+ expr: &ExprAST,
+ named_values: &mut HashMap<&'llvm str, Value<'llvm>>,
+ ) -> CodegenResult<Value<'llvm>> {
+ match expr {
+ ExprAST::Number(num) => Ok(self.module.type_f64().const_f64(*num)),
+ ExprAST::Variable(name) => match named_values.get(name.as_str()) {
+ Some(value) => Ok(*value),
+ None => Err("Unknown variable name".into()),
+ },
+ ExprAST::Binary(binop, lhs, rhs) => {
+ let l = self.codegen_expr(lhs, named_values)?;
+ let r = self.codegen_expr(rhs, named_values)?;
+
+ match binop {
+ '+' => Ok(self.builder.fadd(l, r)),
+ '-' => Ok(self.builder.fsub(l, r)),
+ '*' => Ok(self.builder.fmul(l, r)),
+ '<' => {
+ let res = self.builder.fcmpult(l, r);
+ // Turn bool into f64.
+ Ok(self.builder.uitofp(res, self.module.type_f64()))
+ }
+ _ => Err("invalid binary operator".into()),
+ }
+ }
+ ExprAST::Call(callee, args) => match self.module.get_fn(callee) {
+ Some(callee) => {
+ if callee.args() != args.len() {
+ return Err("Incorrect # arguments passed".into());
+ }
+
+ // Generate code for function argument expressions.
+ let mut args: Vec<Value<'_>> = args
+ .iter()
+ .map(|arg| self.codegen_expr(arg, named_values))
+ .collect::<CodegenResult<_>>()?;
+
+ Ok(self.builder.call(callee, &mut args))
+ }
+ None => Err("Unknown function referenced".into()),
+ },
+ }
+ }
+
+ fn codegen_prototype(&self, PrototypeAST(name, args): &PrototypeAST) -> FnValue<'llvm> {
+ let type_f64 = self.module.type_f64();
+
+ let mut doubles = Vec::new();
+ doubles.resize(args.len(), type_f64);
+
+ // Build the function type: fn(f64, f64, ..) -> f64
+ let ft = self.module.type_fn(&mut doubles, type_f64);
+
+ // Create the function declaration.
+ let f = self.module.add_fn(name, ft);
+
+ // Set the names of the function arguments.
+ for idx in 0..f.args() {
+ f.arg(idx).set_name(&args[idx]);
+ }
+
+ f
+ }
+
+ fn codegen_function(
+ &self,
+ FunctionAST(proto, body): &FunctionAST,
+ named_values: &mut HashMap<&'llvm str, Value<'llvm>>,
+ ) -> CodegenResult<FnValue<'llvm>> {
+ let the_function = match self.module.get_fn(&proto.0) {
+ Some(f) => f,
+ None => self.codegen_prototype(proto),
+ };
+
+ if the_function.basic_blocks() > 0 {
+ return Err("Function cannot be redefined.".into());
+ }
+
+ // Create entry basic block to insert code.
+ let bb = self.module.append_basic_block(the_function);
+ self.builder.pos_at_end(bb);
+
+ // New scope, clear the map with the function args.
+ named_values.clear();
+
+ // Update the map with the current functions args.
+ for idx in 0..the_function.args() {
+ let arg = the_function.arg(idx);
+ named_values.insert(arg.get_name(), arg);
+ }
+
+ // Codegen function body.
+ if let Ok(ret) = self.codegen_expr(body, named_values) {
+ self.builder.ret(ret);
+ assert!(the_function.verify());
+ Ok(the_function)
+ } else {
+ todo!("Failed to codegen function body, erase from module!");
+ }
+ }
+}