Skip to content

Commit

Permalink
feat: if expressions code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra committed Oct 26, 2019
1 parent efdd9fb commit 2968ada
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 41 deletions.
76 changes: 74 additions & 2 deletions crates/mun_codegen/src/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> {
ref callee,
ref args,
} => self.gen_call(*callee, &args).try_as_basic_value().left(),
Expr::If {
condition,
then_branch,
else_branch,
} => self.gen_if(expr, *condition, *then_branch, *else_branch),
_ => unreachable!("unimplemented expr type"),
};

Expand Down Expand Up @@ -365,8 +370,6 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> {
BinaryOp::ArithOp(ArithOp::Multiply) => {
self.builder.build_int_mul(lhs, rhs, "mul").into()
}
// BinaryOp::Remainder => Some(self.gen_remainder(lhs, rhs)),
// BinaryOp::Power =>,
// BinaryOp::Assign,
// BinaryOp::AddAssign,
// BinaryOp::SubtractAssign,
Expand Down Expand Up @@ -436,6 +439,75 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> {
.build_call(*llvm_function, &args, &function.name(self.db).to_string())
}
}

/// Generates IR for an if statement.
fn gen_if(
&mut self,
_expr: ExprId,
condition: ExprId,
then_branch: ExprId,
else_branch: Option<ExprId>,
) -> Option<inkwell::values::BasicValueEnum> {
// Generate IR for the condition
let condition_ir = self
.gen_expr(condition)
.expect("condition must have a value")
.into_int_value();

// Generate the code blocks to branch to
let context = self.module.get_context();
let mut then_block = context.append_basic_block(&self.fn_value, "then");
let else_block_and_expr = match &else_branch {
Some(else_branch) => Some((
context.append_basic_block(&self.fn_value, "else"),
else_branch,
)),
None => None,
};
let merge_block = context.append_basic_block(&self.fn_value, "if_merge");

// Build the actual branching IR for the if statement
let else_block = else_block_and_expr
.as_ref()
.map(|e| &e.0)
.unwrap_or(&merge_block);
self.builder
.build_conditional_branch(condition_ir, &then_block, else_block);

// Fill the then block
self.builder.position_at_end(&then_block);
let then_block_ir = self.gen_expr(then_branch);
self.builder.build_unconditional_branch(&merge_block);
then_block = self.builder.get_insert_block().unwrap();

// Fill the else block, if it exists and get the result back
let else_ir_and_block = if let Some((else_block, else_branch)) = else_block_and_expr {
else_block.move_after(&then_block);
self.builder.position_at_end(&else_block);
let result_ir = self.gen_expr(*else_branch);
self.builder.build_unconditional_branch(&merge_block);
result_ir.map(|res| (res, self.builder.get_insert_block().unwrap()))
} else {
None
};

// Create merge block
merge_block.move_after(&self.builder.get_insert_block().unwrap());
self.builder.position_at_end(&merge_block);

// Construct phi block if a value was returned
if let Some(then_block_ir) = then_block_ir {
if let Some((else_block_ir, else_block)) = else_ir_and_block {
let phi = self.builder.build_phi(then_block_ir.get_type(), "iftmp");
phi.add_incoming(&[(&then_block_ir, &then_block), (&else_block_ir, &else_block)]);
Some(phi.as_basic_value())
} else {
Some(then_block_ir)
}
} else {
None
}
}
}

trait OptName {
Expand Down
31 changes: 31 additions & 0 deletions crates/mun_codegen/src/snapshots/test__fibonacci.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
source: crates/mun_codegen/src/test.rs
expression: "fn fibonacci(n:int):int {\n if n <= 1 {\n n\n } else {\n fibonacci(n-1) + fibonacci(n-2)\n }\n}"
---
; ModuleID = 'main.mun'
source_filename = "main.mun"

%DispatchTable = type { i64 (i64)* }

@dispatchTable = global %DispatchTable { i64 (i64)* @fibonacci }

define i64 @fibonacci(i64 %n) {
body:
%lesseq = icmp sle i64 %n, 1
br i1 %lesseq, label %if_merge, label %else

else: ; preds = %body
%sub = sub i64 %n, 1
%fibonacci_ptr = load i64 (i64)*, i64 (i64)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0)
%fibonacci = call i64 %fibonacci_ptr(i64 %sub)
%sub1 = sub i64 %n, 2
%fibonacci_ptr2 = load i64 (i64)*, i64 (i64)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0)
%fibonacci3 = call i64 %fibonacci_ptr2(i64 %sub1)
%add = add i64 %fibonacci, %fibonacci3
br label %if_merge

if_merge: ; preds = %body, %else
%iftmp = phi i64 [ %add, %else ], [ %n, %body ]
ret i64 %iftmp
}

16 changes: 16 additions & 0 deletions crates/mun_codegen/src/snapshots/test__if_statement.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: crates/mun_codegen/src/test.rs
expression: "fn foo(a:int):int {\n if a > 3 {\n a+1\n } else {\n a-1\n }\n}"
---
; ModuleID = 'main.mun'
source_filename = "main.mun"

define i64 @foo(i64 %a) {
body:
%greater = icmp sgt i64 %a, 3
%add = add i64 %a, 1
%sub = sub i64 %a, 1
%iftmp = select i1 %greater, i64 %add, i64 %sub
ret i64 %iftmp
}

108 changes: 69 additions & 39 deletions crates/mun_codegen/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,6 @@ use mun_hir::SourceDatabase;
use std::cell::RefCell;
use std::sync::Arc;

fn test_snapshot(text: &str) {
let text = text.trim().replace("\n ", "\n");

let (db, file_id) = MockDatabase::with_single_file(&text);

let line_index: Arc<LineIndex> = db.line_index(file_id);
let messages = RefCell::new(Vec::new());
let mut sink = DiagnosticSink::new(|diag| {
let line_col = line_index.line_col(diag.highlight_range().start());
messages.borrow_mut().push(format!(
"error {}:{}: {}",
line_col.line + 1,
line_col.col + 1,
diag.message()
));
});
if let Some(module) = Module::package_modules(&db)
.iter()
.find(|m| m.file_id() == file_id)
{
module.diagnostics(&db, &mut sink)
}
drop(sink);
let messages = messages.into_inner();

let name = if !messages.is_empty() {
messages.join("\n")
} else {
format!(
"{}",
db.module_ir(file_id)
.llvm_module
.print_to_string()
.to_string()
)
};
insta::assert_snapshot!(insta::_macro_support::AutoName, name, &text);
}

#[test]
fn function() {
test_snapshot(
Expand Down Expand Up @@ -185,3 +146,72 @@ fn equality_operands() {
"#,
);
}

#[test]
fn if_statement() {
test_snapshot(
r#"
fn foo(a:int):int {
if a > 3 {
a+1
} else {
a-1
}
}
"#,
)
}

#[test]
fn fibonacci() {
test_snapshot(
r#"
fn fibonacci(n:int):int {
if n <= 1 {
n
} else {
fibonacci(n-1) + fibonacci(n-2)
}
}
"#,
)
}

fn test_snapshot(text: &str) {
let text = text.trim().replace("\n ", "\n");

let (db, file_id) = MockDatabase::with_single_file(&text);

let line_index: Arc<LineIndex> = db.line_index(file_id);
let messages = RefCell::new(Vec::new());
let mut sink = DiagnosticSink::new(|diag| {
let line_col = line_index.line_col(diag.highlight_range().start());
messages.borrow_mut().push(format!(
"error {}:{}: {}",
line_col.line + 1,
line_col.col + 1,
diag.message()
));
});
if let Some(module) = Module::package_modules(&db)
.iter()
.find(|m| m.file_id() == file_id)
{
module.diagnostics(&db, &mut sink)
}
drop(sink);
let messages = messages.into_inner();

let name = if !messages.is_empty() {
messages.join("\n")
} else {
format!(
"{}",
db.module_ir(file_id)
.llvm_module
.print_to_string()
.to_string()
)
};
insta::assert_snapshot!(insta::_macro_support::AutoName, name, &text);
}
28 changes: 28 additions & 0 deletions crates/mun_runtime/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,31 @@ fn booleans() {
false
);
}

#[test]
fn fibonacci() {
let compile_result = compile(
r#"
fn fibonacci(n:int):int {
if n <= 1 {
n
} else {
fibonacci(n-1) + fibonacci(n-2)
}
}
"#,
);
let mut runtime = compile_result.new_runtime();
assert_eq!(
MunRuntime::invoke_fn1::<i64, i64>(&mut runtime, "fibonacci", 5).unwrap(),
5
);
assert_eq!(
MunRuntime::invoke_fn1::<i64, i64>(&mut runtime, "fibonacci", 11).unwrap(),
89
);
assert_eq!(
MunRuntime::invoke_fn1::<i64, i64>(&mut runtime, "fibonacci", 16).unwrap(),
987
);
}

0 comments on commit 2968ada

Please sign in to comment.