Skip to content

Commit

Permalink
LLVM: Support function call expressions (complete expression support)
Browse files Browse the repository at this point in the history
  • Loading branch information
i3abghany committed Nov 1, 2023
1 parent ab25156 commit 8683b3c
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 56 deletions.
65 changes: 54 additions & 11 deletions src/code_generation/llvm/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,34 @@ impl<'ctx> LLVMGenerator<'ctx> {
self.generate_unary_expression(operator, operand)
}
Expression::Assignment(lhs, rhs) => self.generate_assignment(lhs, rhs),
_ => todo!(),
Expression::Parenthesized(expression) => self.generate_expression(expression),
Expression::FunctionCall(func_name, args) => {
self.generate_function_call(&func_name.value, args)
}
Expression::Empty => self
.context
.i32_type()
.const_int(0, false)
.as_basic_value_enum(),
}
}

fn generate_function_call(
&mut self,
name: &str,
args: &Vec<Expression>,
) -> BasicValueEnum<'ctx> {
let function = self.module.get_function(name).unwrap();
let mut arguments = Vec::new();
for arg in args {
arguments.push(self.generate_expression(arg).into());
}
self.builder
.build_call(function, &arguments, "call")
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
}

fn generate_unary_expression(
Expand Down Expand Up @@ -834,16 +860,28 @@ mod tests {
let ast = syntax_analysis::parser::Parser::new(tokens).parse();
let generated_ir =
code_generation::llvm::generator::LLVMGenerator::new(&mut context).generate(&ast);
let exit_code = interpret_llvm_ir(&generated_ir);
assert_eq!(
test_case.expected % 256,
exit_code % 256,
"Test case: {} -- Expected: {}, found: {}\nGenerated IR:\n{}",
test_case.name,
test_case.expected,
exit_code,
generated_ir
);
let (exit_code, stdout_str) = interpret_llvm_ir(&generated_ir);
if test_case.expected_exit_code.is_some() {
let expected_exit_code = test_case.expected_exit_code.unwrap();
assert_eq!(
(expected_exit_code + 256) % 256,
(exit_code + 256) % 256,
"Test case: {} -- Expected exit code: {}, found: {}\nGenerated IR:\n{}",
test_case.name,
expected_exit_code,
exit_code,
generated_ir
);
}

if test_case.expected_output.is_some() {
let expected_output = test_case.expected_output.unwrap();
assert_eq!(
expected_output, stdout_str,
"Test case: {} -- Expected stdout: {}, found: {}\nGenerated IR:\n{}",
test_case.name, expected_output, stdout_str, generated_ir
);
}
}
}

Expand Down Expand Up @@ -887,4 +925,9 @@ mod tests {
fn test_for() {
run_tests_from_file("./src/tests/for.c");
}

#[test]
fn test_function_calls() {
run_tests_from_file("./src/tests/function_calls.c");
}
}
37 changes: 37 additions & 0 deletions src/tests/function_calls.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// CASE Basic function call
// RETURNS 1

int f(int x) {
return x - 1;
}

int main() {
return f(2);
}

// CASE Recursive Fibonacci
// RETURNS 3

int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}

int main() {
return fib(4);
}

// CASE Print to stdout
// RETURNS 0
// Outputs Hello

int putchar(int c);

int main() {
putchar(72);
putchar(101);
putchar(108);
putchar(108);
putchar(111);
return 0;
}
67 changes: 40 additions & 27 deletions src/tests/operators.c
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
// Case Binary operator +
// Returns 3
// CASE Binary operator +
// RETURNS 3

int main() {
return 1 + 2;
}

// Case Binary operator -
// Returns -1
// CASE Binary operator -
// RETURNS -1

int main() {
return 1 - 2;
}

// Case Binary operator *
// Returns 10
// CASE Binary operator *
// RETURNS 10

int main() {
return 5 * 2;
}

// Case Binary operator *
// Returns 4
// CASE Binary operator *
// RETURNS 4

int main() {
return 9 / 2;
}

// Case Binary arithmetic operators combined
// Returns 9697
// CASE Binary arithmetic operators combined
// RETURNS 9697

int main() {
int x = 312;
Expand All @@ -37,43 +37,56 @@ int main() {
return z - x;
}

// Case Binary operator ||
// Returns 1
// CASE Binary operator ||
// RETURNS 1

int main() {
int false = 0; int true = 123; return true || false;
int false = 0; int true = 123; int y = true || false; return y;
}

// Case Binary operator &&
// Returns 0
// CASE Binary operator &&
// RETURNS 0

int main() {
int false = 0; int true = 123; return true && false;
int false = 0; int true = 123; int y = true && false; return y;
}

// Case Binary operator ^
// Returns 123
// CASE Binary operator ^
// RETURNS 123

int main() {
int false = 0; int true = 123; return true ^ false;
}

// Case Unary operator ! 1
// Returns 1
// CASE Unary operator ! 1
// RETURNS 1

int main() {
return !0;
int x = !0;
return x;
}

// Case Unary operator ! 2
// Returns 0
// CASE Unary operator ! 2
// RETURNS 0

int main() {
return !1;
int x = !1;
return x;
}

// Case Unary +-
// Returns 144
// CASE Unary +-
// RETURNS 144

int main() {
int x = -----++++----+------12; return x * x;
}
}

// CASE Binary, unary and parenthesized expressions
// RETURNS -1

int main() {
int x = 3;
int y = 4;
int z = 5;
return (-x + y) * (z - x) / (-y + z) - x;
}
56 changes: 38 additions & 18 deletions src/utils/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ pub fn expect_exit_code(source: String, expected: i32) -> std::io::Result<()> {
pub struct TestCase {
pub name: String,
pub source: String,
pub expected: i32,
pub expected_exit_code: Option<i32>,
pub expected_output: Option<String>,
}

pub fn parse_test_file(path: &str) -> Vec<TestCase> {
Expand All @@ -56,26 +57,44 @@ pub fn parse_test_file(path: &str) -> Vec<TestCase> {
let test_strings = contents.split("// CASE ").skip(1).collect::<Vec<_>>();
let mut result = vec![];
for test_string in test_strings {
let mut lines = test_string.lines();
let name = lines.next().unwrap().clone().to_string();
let expected = lines
.next()
.unwrap()
.strip_prefix("// RETURNS ")
.unwrap()
.parse::<i32>()
.unwrap();
let source = lines.collect::<Vec<_>>().join("\n");
result.push(TestCase {
name,
source,
expected,
});
let mut lines = test_string.lines().peekable();

let mut test_case = TestCase {
name: lines.next().unwrap().to_string(),
source: "".to_string(),
expected_exit_code: None,
expected_output: None,
};

while lines.peek().is_some() {
let line = lines.peek().unwrap().to_string();

if line.is_empty() {
lines.next().unwrap();
continue;
}

let line = line.split_ascii_whitespace().collect::<Vec<_>>();
if line[0] != "//" {
break;
}

match line[1].to_ascii_lowercase().as_str() {
"returns" => test_case.expected_exit_code = Some(line[2].parse::<i32>().unwrap()),
"outputs" => test_case.expected_output = Some(line[2..].join(" ")),
_ => panic!("Invalid test case metadata: {}", line.join(" ")),
}

lines.next().unwrap();
}

test_case.source = lines.collect::<Vec<_>>().join("\n");
result.push(test_case);
}
result
}

pub fn interpret_llvm_ir(ir: &str) -> i32 {
pub fn interpret_llvm_ir(ir: &str) -> (i32, String) {
let id = Uuid::new_v4();
let ir_path = format!("./{}.ll", id);
let mut ir_file = File::create(&ir_path).unwrap();
Expand All @@ -87,6 +106,7 @@ pub fn interpret_llvm_ir(ir: &str) -> i32 {
.output()
.expect("Failed to compile generated code");
let exit_code = output.status;
let stdout_str = String::from_utf8(output.stdout).unwrap();
remove_file(&ir_path).unwrap();
return exit_code.code().unwrap();
return (exit_code.code().unwrap(), stdout_str);
}

0 comments on commit 8683b3c

Please sign in to comment.