Skip to content

Commit

Permalink
fix(builder): fib example now works as expected
Browse files Browse the repository at this point in the history
  • Loading branch information
0xLucqs committed Jul 19, 2024
1 parent 887b091 commit 9f5a630
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 154 deletions.
2 changes: 1 addition & 1 deletion examples/fib/fib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[no_mangle]
pub fn fib(a: u32, b: u32, n: u32) -> u32 {
pub fn fib(a: u128, b: u128, n: u128) -> u128 {
if n == 0 { a } else { fib(b, a + b, n - 1) }
}
52 changes: 27 additions & 25 deletions src/builder/function/binary.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use inkwell::basic_block::BasicBlock;
use inkwell::values::{AnyValue, BasicValueEnum, InstructionValue, IntValue};

use super::CairoFunctionBuilder;
use crate::builder::get_name;

impl<'ctx> CairoFunctionBuilder<'ctx> {
fn extract_const_int_value(val: IntValue) -> String {
Expand All @@ -27,7 +27,7 @@ impl<'ctx> CairoFunctionBuilder<'ctx> {
&mut self,
instruction: &InstructionValue<'ctx>,
operator: &str,
is_loop: bool,
bb: &BasicBlock<'ctx>,
) -> String {
// Get th left operand.
let left = unsafe {
Expand All @@ -49,33 +49,35 @@ impl<'ctx> CairoFunctionBuilder<'ctx> {

let instr_name = {
let basic_val: BasicValueEnum = instruction.as_any_value_enum().try_into().unwrap();
self.variables
.get(&basic_val)
.cloned()
.unwrap_or_else(|| get_name(instruction.get_name().unwrap_or_default()).unwrap_or("result".to_owned()))
// Try to get the variable from our variables mapping. If not found create it and insert it in the
// mmaping.
self.variables.get(&basic_val).cloned().unwrap_or_else(|| {
let instr_name = self.get_name(instruction.get_name().unwrap_or_default());
if let Ok(basic_value_enum) = instruction.as_any_value_enum().try_into() {
// Save the result variable in our mapping to be able to use later.
self.variables.insert(basic_value_enum, instr_name.clone());
}
format!("let {}", instr_name)
})
};
if let Ok(basic_value_enum) = instruction.as_any_value_enum().try_into() {
// Save the result variable in our mapping to be able to use later.
self.variables.insert(basic_value_enum, instr_name.clone());
}

let annoying_phis = self.bblock_variables.get(bb).cloned().unwrap_or_default();
// The operand is either a variable or a constant so either we get it from our mapping or it's
// unnamed and it's a const literal.
let left_name = get_name(left.get_name()).unwrap_or_else(|| {
if left.into_int_value().is_const() {
Self::extract_const_int_value(left.into_int_value())
} else {
unreachable!("Left operand should either be a variable or a constant")
}
});
let right_name = get_name(right.get_name()).unwrap_or_else(|| {
if right.into_int_value().is_const() {
Self::extract_const_int_value(right.into_int_value())
} else {
unreachable!("Right should either be a variable or a constant")
}
});
// TODO(Lucas): a variable can surely be in the variables mapping, try to get it from there as well.
let left_name = if left.into_int_value().is_const() {
Self::extract_const_int_value(left.into_int_value())
} else {
// If it's not a const might be in our annoying phi mapping.
annoying_phis.get(&left).cloned().unwrap_or_else(|| self.get_name(left.get_name()))
};
let right_name = if right.into_int_value().is_const() {
Self::extract_const_int_value(right.into_int_value())
} else {
// If it's not a const might be in our annoying phi mapping.
annoying_phis.get(&right).cloned().unwrap_or_else(|| self.get_name(right.get_name()))
};

format!("{}{} = {} {} {};", if is_loop { "" } else { "let " }, instr_name, left_name, operator, right_name)
format!("{} = {} {} {};", instr_name, left_name, operator, right_name)
}
}
46 changes: 37 additions & 9 deletions src/builder/function/branch.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
use inkwell::basic_block::BasicBlock;
use inkwell::values::InstructionValue;

use super::CairoFunctionBuilder;

impl<'ctx> CairoFunctionBuilder<'ctx> {
pub fn process_branch(&mut self, instruction: &InstructionValue<'ctx>, is_loop: &bool) -> String {
let cond = instruction.get_operand(0).unwrap().left().unwrap();
// If we're in a loop this is the exit condition so we break.
if *is_loop {
format!("if {}\n{{break;}}", self.variables.get(&cond).unwrap())
/// Process a branch instruction. If there is only 1 operand without condition it'll translate
/// the basic block it jumps to and will move on to the next basic block.
/// If there is an if/else it will process all the basic blocks that are involved.
pub fn process_branch(
&mut self,
instruction: &InstructionValue<'ctx>,
bb: &BasicBlock<'ctx>,
is_loop: &bool,
is_else: &bool,
) -> String {
// Get all the annoying variables that require to be declared in a bigger scope and will update
// their value.
self.bblock_variables.get(bb).cloned().unwrap_or_default().into_values().for_each(|val| {
self.push_body_line(format!("{} = {};", val.trim_end_matches("_temp"), val));
});
self.set_basic_block_booleans(bb);
// Case were there is an inconditionnal jump.
if instruction.get_num_operands() == 1 {
self.process_basic_block(&instruction.get_operand(0).unwrap().right().unwrap());
"".to_owned()
} else {
// else it means that we're in a if/else case and the first block is the if the 2nd is the else.
self.if_blocks.insert(instruction.get_operand(1).unwrap().right().unwrap(), cond);
self.else_blocks.insert(instruction.get_operand(2).unwrap().right().unwrap());
// There is a condition could either be a loop break or if/else
let cond = instruction.get_operand(0).unwrap().left().unwrap();
// If we're in a loop this is the exit condition so we break.
if *is_loop {
let res = format!("if {}\n{{break;}}", self.variables.get(&cond).unwrap());

"".to_owned()
res
} else {
self.close_scopes(bb, is_else, is_loop);
// else it means that we're in a if/else case and the first block is the if the 2nd is the else.
self.if_blocks.insert(instruction.get_operand(1).unwrap().right().unwrap(), cond);
self.process_basic_block(&instruction.get_operand(1).unwrap().right().unwrap());
self.else_blocks.insert(instruction.get_operand(2).unwrap().right().unwrap());
self.process_basic_block(&instruction.get_operand(2).unwrap().right().unwrap());

"".to_owned()
}
}
}
}
73 changes: 72 additions & 1 deletion src/builder/function/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::collections::{HashMap, HashSet};
use std::ffi::CStr;
use std::fmt::Display;

use inkwell::basic_block::BasicBlock;
use inkwell::values::BasicValueEnum;
use inkwell::values::{BasicValueEnum, InstructionOpcode};
use inkwell::IntPredicate;
use petgraph::graph::{DiGraph, NodeIndex};

pub mod binary;
pub mod branch;
pub mod extend;
pub mod phi;
pub mod preprocessing;
pub mod types;
Expand All @@ -19,6 +22,7 @@ pub struct CairoFunctionBuilder<'ctx> {
pub(crate) node_id_from_name: HashMap<BasicBlock<'ctx>, NodeIndex<u32>>,
pub(crate) function: CairoFunction,
pub(crate) phis_bblock: HashSet<BasicBlock<'ctx>>,
pub(crate) bblock_variables: HashMap<BasicBlock<'ctx>, HashMap<BasicValueEnum<'ctx>, String>>,
pub(crate) if_blocks: HashMap<BasicBlock<'ctx>, BasicValueEnum<'ctx>>,
pub(crate) else_blocks: HashSet<BasicBlock<'ctx>>,
pub(crate) return_block: Option<BasicBlock<'ctx>>,
Expand All @@ -40,6 +44,73 @@ impl<'ctx> CairoFunctionBuilder<'ctx> {
pub fn push_body_line(&mut self, line: String) {
self.function.body.push_line(line)
}
pub fn get_name(&self, name: &CStr) -> String {
(!name.is_empty())
.then(|| name.to_str().expect("Variable name for binary op should be uft-8").replace('.', "_"))
.unwrap_or_else(|| format!("var{}", self.variables.keys().count()))
}

/// Set all the basic block booleans to the correct value. This should be used at the end of a
/// basic block before jump to know from which basic block we're coming from at runtime.
pub fn set_basic_block_booleans(&mut self, bb: &BasicBlock<'ctx>) {
// If we're not in the last basic block set all the booleans to the right value to know what basic
// block we were in so we can process the phi instruction can work correctly
if self.return_block.is_some_and(|bblock| bblock != *bb) || self.return_block.is_none() {
for bblock in self.phis_bblock.iter() {
// If we were in this basic block
if self.get_name(bblock.get_name()) == self.get_name(bb.get_name()) {
let code_line = format!("is_from_{} = true;", self.get_name(bblock.get_name()),);
self.function.body.push_line(code_line);
} else {
// if we were not in this basic block
let code_line = format!("is_from_{} = false;", self.get_name(bblock.get_name()),);
self.function.body.push_line(code_line);
}
}
}
}
/// Process a basic block and convert it to cairo. It will call itself recursively through the
/// [CairoFunctionBuilder::process_branch] function.
pub fn process_basic_block(&mut self, bb: &BasicBlock<'ctx>) {
// Boolean that let's us know if we need to wrap our basic block translation with a loop { bbcode };
let is_loop = self.bb_loop.contains(bb);
// Is this block the else clause of an if/else
let is_else = self.else_blocks.contains(bb);
// TODO(Lucas): in preprocess function declare all the variables that will be in subscopes and then
// stop worrying about it + only use is_subscope
let _is_subscope = is_loop || is_else || self.if_blocks.contains_key(bb);

// Prepare for loops/if/else
self.prepare_new_scopes(bb, &is_else, &is_loop);

// Iterate over each instruction of the basic block. 1 instruction == 1 LLVM code line
for instruction in bb.get_instructions() {
// Get the opcode of the instruction
let code_line = match instruction.get_opcode() {
InstructionOpcode::Add => self.process_binary_int_op(&instruction, "+", bb),
InstructionOpcode::Sub => self.process_binary_int_op(&instruction, "-", bb),
InstructionOpcode::Return => self.process_return(&instruction),
InstructionOpcode::ICmp => {
// we just matched on ICmp so it will never fail
match instruction.get_icmp_predicate().unwrap() {
IntPredicate::EQ => self.process_binary_int_op(&instruction, "==", bb),
IntPredicate::NE => self.process_binary_int_op(&instruction, "!=", bb),
IntPredicate::ULT => self.process_binary_int_op(&instruction, "<", bb),
_ => "".to_owned(),
}
}
InstructionOpcode::Br => self.process_branch(&instruction, bb, &is_loop, &is_else),
InstructionOpcode::ZExt => self.process_zext(&instruction, &is_loop),
InstructionOpcode::Phi => self.process_phi(&instruction, bb),
_ => "".to_owned(),
};
self.push_body_line(code_line);
if is_loop && instruction.get_opcode() == InstructionOpcode::Br {
self.close_scopes(bb, &is_else, &is_loop);
}
// Add the line to the function body
}
}
}

#[derive(Default, Clone, PartialEq, Debug)]
Expand Down
48 changes: 32 additions & 16 deletions src/builder/function/phi.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,52 @@
use inkwell::values::{AsValueRef, InstructionValue, PhiValue};
use inkwell::basic_block::BasicBlock;
use inkwell::values::{AsValueRef, BasicValueEnum, InstructionValue, PhiValue};

use super::CairoFunctionBuilder;
use crate::builder::get_name;

impl<'ctx> CairoFunctionBuilder<'ctx> {
pub fn process_phi(&mut self, instruction: &InstructionValue<'ctx>, is_loop: &bool) -> String {
pub fn process_phi(&mut self, instruction: &InstructionValue<'ctx>, bb: &BasicBlock<'ctx>) -> String {
let annoying_phis = self.bblock_variables.get(bb).cloned().unwrap_or_default();
let phi = unsafe { PhiValue::new(instruction.as_value_ref()) };
// name of the result variable
let phi_name = get_name(phi.get_name()).unwrap(); // variable to store the result in
let phi_name = annoying_phis
.get(unsafe { &BasicValueEnum::new(instruction.as_value_ref()) })
.cloned()
.unwrap_or_else(|| {
let name = self.get_name(phi.get_name());
// if it was not in the mapping insert it. In theory we could insert it in any case but we don't
// want to do that it would poisin the regular variable mapping with the annoying phis and would
// mess everything up
self.variables.insert(phi.as_basic_value(), name.clone());
name
}); // variable to store the result in

// Incomming values (basic block + variable to set the value to)
let first = phi.get_incoming(0).unwrap();
// Name of the variable we should set the value to.
let left_var = get_name(first.0.get_name()).unwrap(); // phi left variable
let left_var = self.variables.get(&first.0).cloned().unwrap_or_else(|| {
let name = self.get_name(first.0.get_name());
self.variables.insert(first.0, name.clone());
name
}); // phi right variable

// Incomming values (basic block + variable to set the value to)
let second = phi.get_incoming(1).unwrap();
// Name of the variable we should set the value to.
let right_var = get_name(second.0.get_name()).unwrap(); // phi right variable

self.variables.insert(first.0, left_var.clone());
self.variables.insert(second.0, right_var.clone());
self.variables.insert(phi.as_basic_value(), right_var.clone());
let right_var = self.variables.get(&second.0).cloned().unwrap_or_else(|| {
let name = self.get_name(second.0.get_name());
self.variables.insert(second.0, name.clone());
name
}); // phi right variable
// If we're in a subscope we don't need the `let` because we declared the variable above the scope.
format!(
"{}{} = if is_from_{} {{ {} }} else if is_from_{} {{ {} }} else {{ panic!(\"There is a bug in the \
compiler please report it\")}};",
if *is_loop { "" } else { "let " },
"let {} = if is_from_{} {{ {} }} else if is_from_{} {{ {} }} else {{ panic!(\"There is a bug in the \
compiler at var {} please report it\")}};",
phi_name,
get_name(first.1.get_name()).unwrap(), // phi left basic block
self.get_name(first.1.get_name()), // phi left basic block
left_var,
get_name(second.1.get_name()).unwrap(), // phi right basic block
right_var
self.get_name(second.1.get_name()), // phi right basic block
right_var,
phi_name
)
}
}
Loading

0 comments on commit 9f5a630

Please sign in to comment.