use alloc::string::String;
use alloc::vec::Vec;
use crate::entity::{Block, Value};
use crate::function::{BlockData, Function, ValueData, ValueDef};
use crate::inst::{BinOp, Inst, Terminator, UnOp};
use crate::ty::Type;
pub struct Builder {
name: String,
params: Vec<Type>,
ret: Type,
entry: Block,
blocks: Vec<BlockData>,
values: Vec<ValueData>,
current: Block,
}
impl Builder {
#[must_use]
pub fn new(name: impl Into<String>, params: &[Type], ret: Type) -> Self {
let entry = Block::from_raw(0);
let mut values = Vec::with_capacity(params.len());
let mut entry_params = Vec::with_capacity(params.len());
for &ty in params {
let value = Value::from_raw(values.len() as u32);
values.push(ValueData {
ty,
def: ValueDef::Param(entry),
});
entry_params.push(value);
}
let entry_data = BlockData {
params: entry_params,
insts: Vec::new(),
term: None,
};
Self {
name: name.into(),
params: params.to_vec(),
ret,
entry,
blocks: alloc::vec![entry_data],
values,
current: entry,
}
}
#[must_use]
pub const fn entry(&self) -> Block {
self.entry
}
#[must_use]
pub const fn current_block(&self) -> Block {
self.current
}
pub fn create_block(&mut self, params: &[Type]) -> Block {
let block = Block::from_raw(self.blocks.len() as u32);
let mut block_params = Vec::with_capacity(params.len());
for &ty in params {
let value = Value::from_raw(self.values.len() as u32);
self.values.push(ValueData {
ty,
def: ValueDef::Param(block),
});
block_params.push(value);
}
self.blocks.push(BlockData {
params: block_params,
insts: Vec::new(),
term: None,
});
block
}
#[must_use]
pub fn block_params(&self, block: Block) -> &[Value] {
match self.blocks.get(block.index()) {
Some(data) => &data.params,
None => &[],
}
}
pub fn switch_to(&mut self, block: Block) {
self.current = block;
}
pub fn iconst(&mut self, value: i64) -> Value {
self.push_inst(Inst::Iconst(value), Type::Int)
}
pub fn fconst(&mut self, value: f64) -> Value {
self.push_inst(Inst::Fconst(value), Type::Float)
}
pub fn bconst(&mut self, value: bool) -> Value {
self.push_inst(Inst::Bconst(value), Type::Bool)
}
pub fn bin(&mut self, op: BinOp, lhs: Value, rhs: Value) -> Value {
let ty = if op.is_comparison() || op.is_logical() {
Type::Bool
} else {
self.value_ty(lhs)
};
self.push_inst(Inst::Bin(op, lhs, rhs), ty)
}
pub fn un(&mut self, op: UnOp, operand: Value) -> Value {
let ty = match op {
UnOp::Neg => self.value_ty(operand),
UnOp::Not => Type::Bool,
};
self.push_inst(Inst::Un(op, operand), ty)
}
pub fn ret(&mut self, value: Option<Value>) {
self.set_terminator(Terminator::Return(value));
}
pub fn jump(&mut self, target: Block, args: &[Value]) {
self.set_terminator(Terminator::Jump(target, args.to_vec()));
}
pub fn branch(
&mut self,
cond: Value,
then_block: Block,
then_args: &[Value],
else_block: Block,
else_args: &[Value],
) {
self.set_terminator(Terminator::Branch {
cond,
then_block,
then_args: then_args.to_vec(),
else_block,
else_args: else_args.to_vec(),
});
}
#[must_use]
pub fn finish(self) -> Function {
Function::from_parts(
self.name,
self.params,
self.ret,
self.entry,
self.blocks,
self.values,
)
}
fn value_ty(&self, value: Value) -> Type {
self.values
.get(value.index())
.map_or(Type::Unit, |data| data.ty)
}
fn push_inst(&mut self, inst: Inst, ty: Type) -> Value {
let value = Value::from_raw(self.values.len() as u32);
self.values.push(ValueData {
ty,
def: ValueDef::Inst(self.current, inst),
});
if let Some(block) = self.blocks.get_mut(self.current.index()) {
block.insts.push(value);
}
value
}
fn set_terminator(&mut self, term: Terminator) {
if let Some(block) = self.blocks.get_mut(self.current.index()) {
block.term = Some(term);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_seeds_entry_with_function_params() {
let b = Builder::new("f", &[Type::Int, Type::Bool], Type::Unit);
let params = b.block_params(b.entry());
assert_eq!(params.len(), 2);
assert_eq!(b.current_block(), b.entry());
}
#[test]
fn test_bin_result_type_follows_operation() {
let mut b = Builder::new("f", &[Type::Int, Type::Int], Type::Bool);
let a = b.block_params(b.entry())[0];
let c = b.block_params(b.entry())[1];
let sum = b.bin(BinOp::Add, a, c);
let cmp = b.bin(BinOp::Lt, a, c);
let and = b.bin(BinOp::And, cmp, cmp);
b.ret(Some(cmp));
let func = b.finish();
assert_eq!(func.value_type(sum), Some(Type::Int));
assert_eq!(func.value_type(cmp), Some(Type::Bool));
assert_eq!(func.value_type(and), Some(Type::Bool));
}
#[test]
fn test_un_result_type_follows_operation() {
let mut b = Builder::new("f", &[Type::Int, Type::Bool], Type::Int);
let x = b.block_params(b.entry())[0];
let flag = b.block_params(b.entry())[1];
let neg = b.un(UnOp::Neg, x);
let not = b.un(UnOp::Not, flag);
b.ret(Some(neg));
let func = b.finish();
assert_eq!(func.value_type(neg), Some(Type::Int));
assert_eq!(func.value_type(not), Some(Type::Bool));
}
#[test]
fn test_handles_are_dense_from_zero() {
let mut b = Builder::new("f", &[Type::Int], Type::Int);
let p = b.block_params(b.entry())[0];
let one = b.iconst(1);
let two = b.iconst(2);
assert_eq!(p.index(), 0);
assert_eq!(one.index(), 1);
assert_eq!(two.index(), 2);
}
}