Skip to content

Commit

Permalink
feat(builder): add stuff needed for fib
Browse files Browse the repository at this point in the history
  • Loading branch information
0xLucqs committed Jul 18, 2024
1 parent 99349c9 commit e8d9b62
Show file tree
Hide file tree
Showing 11 changed files with 468 additions and 90 deletions.
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ edition = "2021"

[dependencies]
inkwell = {git = "https://github.com/TheDan64/inkwell", features = ["llvm18-0"]}
petgraph = "0.6.5"
4 changes: 4 additions & 0 deletions examples/fib/fib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[no_mangle]
pub fn fib(a: u32, b: u32, n: u32) -> u32 {
if n == 0 { a } else { fib(b, a + b, n - 1) }
}
28 changes: 20 additions & 8 deletions src/builder/function/binary.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use inkwell::values::{AnyValue, InstructionValue, IntValue};
use inkwell::values::{AnyValue, BasicValueEnum, InstructionValue, IntValue};

use super::CairoFunctionBuilder;
use crate::builder::get_name;
Expand All @@ -13,7 +13,7 @@ impl<'ctx> CairoFunctionBuilder<'ctx> {
.last()
.unwrap()
// Sanity check
.parse::<u128>()
.parse::<i128>()
.expect("Rust doesn't handle numbers bigger than u128");
// Then get the type
let ty = val.get_type().print_to_string().to_string();
Expand All @@ -23,7 +23,12 @@ impl<'ctx> CairoFunctionBuilder<'ctx> {
}
/// Translates an LLVM binary operation to cairo. This can be anything that expects exactly 1
/// operator with a left and right operand.
pub fn process_binary_int_op(&mut self, instruction: &InstructionValue<'ctx>, operator: &str) -> String {
pub fn process_binary_int_op(
&mut self,
instruction: &InstructionValue<'ctx>,
operator: &str,
is_loop: bool,
) -> String {
// Get th left operand.
let left = unsafe {
instruction
Expand All @@ -41,29 +46,36 @@ impl<'ctx> CairoFunctionBuilder<'ctx> {
.expect("right operand of add should be a basic value")
};
// Get the name of the variable we want to store the result of the operantion in.
let instr_name = get_name(instruction.get_name().unwrap_or_default()).unwrap_or("result".to_owned());

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()))
};
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());
}

// 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 = self.variables.get(&left).cloned().unwrap_or_else(|| {
if right.into_int_value().is_const() {
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 = self.variables.get(&right).cloned().unwrap_or_else(|| {
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")
}
});

format!("let {} = {} {} {};", instr_name, left_name, operator, right_name)
format!("{}{} = {} {} {};", if is_loop { "" } else { "let " }, instr_name, left_name, operator, right_name)
}
}
19 changes: 19 additions & 0 deletions src/builder/function/branch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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())
} 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());

"".to_owned()
}
}
}
108 changes: 34 additions & 74 deletions src/builder/function/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;

use inkwell::values::{BasicValueEnum, FunctionValue, InstructionValue};
use inkwell::basic_block::BasicBlock;
use inkwell::values::BasicValueEnum;
use petgraph::graph::{DiGraph, NodeIndex};

use super::get_name;
pub mod binary;
pub mod branch;
pub mod phi;
pub mod preprocessing;
pub mod types;

#[derive(Default, Clone, PartialEq, Debug)]
#[derive(Default, Clone, Debug)]
pub struct CairoFunctionBuilder<'ctx> {
pub(crate) bb_loop: HashSet<BasicBlock<'ctx>>,
pub(crate) variables: HashMap<BasicValueEnum<'ctx>, String>,
pub(crate) bb_graph: DiGraph<BasicBlock<'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) if_blocks: HashMap<BasicBlock<'ctx>, BasicValueEnum<'ctx>>,
pub(crate) else_blocks: HashSet<BasicBlock<'ctx>>,
pub(crate) return_block: Option<BasicBlock<'ctx>>,
}

impl<'ctx> CairoFunctionBuilder<'ctx> {
pub fn name(&self) -> &str {
&self.function.signature.name
}

pub fn arg(&self, parameter_nb: usize) -> &CairoParameter {
&self.function.signature.parameters.0[parameter_nb]
}

pub fn return_type(&self) -> &str {
&self.function.signature.return_type
}

pub fn push_body_line(&mut self, line: String) {
self.function.body.push_line(line)
}
}

#[derive(Default, Clone, PartialEq, Debug)]
Expand Down Expand Up @@ -89,73 +119,3 @@ impl Display for CairoParameter {
f.write_str(&format!("{}: {}", self.name, self.ty))
}
}

impl<'ctx> CairoFunctionBuilder<'ctx> {
pub fn name(&self) -> &str {
&self.function.signature.name
}

pub fn arg(&self, parameter_nb: usize) -> &CairoParameter {
&self.function.signature.parameters.0[parameter_nb]
}

pub fn return_type(&self) -> &str {
&self.function.signature.return_type
}
}

impl<'ctx> CairoFunctionBuilder<'ctx> {
/// Translate the LLVM function signature into a Cairo function signature.
///
/// # Arguments
///
/// * `function` - The function we want to translate the signature of.
/// * `fn_id` - Is the index of the function in our file but it can be any number it's just in
/// case the llvm function name is empty.
///
/// # Returns
///
/// * `String` - The cairo function signature in the form
/// `pub fn <name>(<param1>: <type1>,<param2>: <type2>,) -> <return_type>`
pub fn process_function_signature(
&mut self,
function: &FunctionValue<'ctx>,
fn_id: usize,
) -> CairoFunctionSignature {
// Get the function name and if it's empty call it "function{fn_id}"
let name = get_name(function.get_name()).unwrap_or(format!("function{fn_id}"));
let mut parameters = Vec::<CairoParameter>::with_capacity(function.count_params() as usize);
// Extract each parameter and its type.
function.get_param_iter().enumerate().for_each(|(index, param)| {
let param_name = get_name(param.get_name()).unwrap_or(index.to_string());
let param_type = param.get_type().print_to_string().to_string();
self.variables.insert(param, param_name.clone());
parameters.push(CairoParameter { name: param_name, ty: param_type });
});
// Get the return type of the function. If it's Some it means that the function returns a value else
// it returns void.
let return_type = if let Some(ty) = function.get_type().get_return_type() {
ty.print_to_string().to_string()
} else {
"()".to_string()
};
CairoFunctionSignature::new(name, parameters, return_type)
}

/// Translate an LLVM Return instruction in cairo.
pub fn process_return(&mut self, instruction: &InstructionValue) -> String {
format!(
"return {};",
self.variables
.get(
&instruction
.get_operand(0)
.expect("Return opcode should have exactly 1 operand")
.left()
.expect("Return can only return a value hence left")
)
// TODO handle const
.expect("Return a variable")
)
}
}
36 changes: 36 additions & 0 deletions src/builder/function/phi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use inkwell::values::{AsValueRef, 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 {
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
// 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

// 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());
// 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 " },
phi_name,
get_name(first.1.get_name()).unwrap(), // phi left basic block
left_var,
get_name(second.1.get_name()).unwrap(), // phi right basic block
right_var
)
}
}
Loading

0 comments on commit e8d9b62

Please sign in to comment.