Casting, GEPs, partial LLVM backend
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "leaf_backend_llvm"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
arcstr = "1.2.0"
|
||||
fxhash = "0.2.1"
|
||||
leaf_assembly = { path = "../../assembly" }
|
||||
inkwell = { version = "0.8.0", default-features = false, features = ["llvm21-1", "target-aarch64", "target-x86"] }
|
||||
derive_more = { version = "2.0.1", features = ["deref", "deref_mut", "debug", "display", "try_from", "from", "try_into", "into"] }
|
||||
scc = "3.6.6"
|
||||
@@ -0,0 +1,395 @@
|
||||
use fxhash::{FxBuildHasher, FxHashMap};
|
||||
pub use inkwell;
|
||||
use inkwell::{
|
||||
AddressSpace, IntPredicate,
|
||||
module::Module,
|
||||
targets::TargetMachine,
|
||||
types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicTypeEnum, IntType},
|
||||
values::{AnyValue, BasicValue, BasicValueEnum},
|
||||
};
|
||||
use leaf_assembly::{
|
||||
assembly::Assembly,
|
||||
functions::{
|
||||
Function,
|
||||
ir::{Cmp, Instruction, InstructionVariant},
|
||||
},
|
||||
types::{
|
||||
Type,
|
||||
derivations::{ArrayT, FuncT, PtrT, RefT},
|
||||
},
|
||||
values::{AnyConst, Int, Value},
|
||||
};
|
||||
use scc::HashMap;
|
||||
|
||||
pub type LlvmContext = inkwell::context::Context;
|
||||
pub type LlvmModule<'l> = inkwell::module::Module<'l>;
|
||||
pub type LlvmFunction<'l> = inkwell::values::FunctionValue<'l>;
|
||||
pub type LlvmType<'l> = inkwell::types::AnyTypeEnum<'l>;
|
||||
|
||||
pub struct CompilationContext<'l> {
|
||||
ctx: &'l LlvmContext,
|
||||
native_int_ty: IntType<'l>,
|
||||
types: HashMap<Type<'l>, AnyTypeEnum<'l>, FxBuildHasher>,
|
||||
modules: HashMap<&'l Assembly<'l>, Module<'l>, FxBuildHasher>,
|
||||
functions: HashMap<(&'l Assembly<'l>, &'l Function<'l>), LlvmFunction<'l>, FxBuildHasher>,
|
||||
}
|
||||
|
||||
impl<'l> CompilationContext<'l> {
|
||||
pub fn new(ctx: &'l LlvmContext, target: &TargetMachine) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
types: HashMap::default(),
|
||||
modules: HashMap::default(),
|
||||
functions: HashMap::default(),
|
||||
native_int_ty: match target.get_target_data().get_pointer_byte_size(None) {
|
||||
8 => ctx.i8_type().into(),
|
||||
16 => ctx.i16_type().into(),
|
||||
32 => ctx.i32_type().into(),
|
||||
64 => ctx.i64_type().into(),
|
||||
128 => ctx.i128_type().into(),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(&self, assembly: &'l Assembly<'l>) -> LlvmModule<'l> {
|
||||
let mut exists = true;
|
||||
let module = self
|
||||
.modules
|
||||
.entry_sync(assembly)
|
||||
.or_insert_with(|| {
|
||||
exists = false;
|
||||
self.ctx.create_module(&assembly.ident().to_string())
|
||||
})
|
||||
.clone();
|
||||
|
||||
if exists {
|
||||
return module;
|
||||
}
|
||||
|
||||
for (i, func) in assembly.functions() {
|
||||
let ty = self.get_type(Type::Func(func.ty)).into_function_type();
|
||||
let name = match func.name.get() {
|
||||
Some(n) => *n,
|
||||
None => &format!("<fn_{i}>"),
|
||||
};
|
||||
|
||||
self.functions
|
||||
.entry_sync((assembly, func))
|
||||
.or_insert_with(|| module.add_function(name, ty, None));
|
||||
}
|
||||
|
||||
for (i, func) in assembly.functions() {
|
||||
let llvm_func = self.functions.get_sync(&(assembly, *func)).unwrap().clone();
|
||||
|
||||
let Some(body) = func.body() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut values = FxHashMap::<Value, Option<BasicValueEnum>>::default();
|
||||
for (i, ty) in func.ty.par_t.iter().enumerate() {
|
||||
let ty = self.get_type(*ty);
|
||||
if BasicMetadataTypeEnum::try_from(ty).is_ok() {
|
||||
values.insert(
|
||||
Value::Parameter(i, func),
|
||||
Some(llvm_func.get_nth_param(values.len() as u32).unwrap()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut blocks = vec![];
|
||||
let builder = self.ctx.create_builder();
|
||||
for block in &body.blocks {
|
||||
blocks.push(
|
||||
self.ctx
|
||||
.append_basic_block(llvm_func, &format!("block_{}", block.id)),
|
||||
);
|
||||
}
|
||||
for block in &body.blocks {
|
||||
builder.position_at_end(blocks[block.id as usize]);
|
||||
for inst @ Instruction { variant, .. } in block.instructions() {
|
||||
let llvm_value = match variant {
|
||||
InstructionVariant::StackAlloc(ty) => {
|
||||
let ty = self.get_type(*ty);
|
||||
BasicTypeEnum::try_from(ty)
|
||||
.ok()
|
||||
.map(|ty| builder.build_alloca(ty, "").unwrap().into())
|
||||
}
|
||||
|
||||
InstructionVariant::Load(value) => 'val: {
|
||||
let Type::Ptr(PtrT { base, .. }) = value.ty() else {
|
||||
unreachable!()
|
||||
};
|
||||
let Ok(ty): Result<BasicTypeEnum, _> = self.get_type(*base).try_into()
|
||||
else {
|
||||
break 'val None;
|
||||
};
|
||||
let Some(value) = self.get_value(&values, value) else {
|
||||
break 'val None;
|
||||
};
|
||||
let ptr = value.into_pointer_value();
|
||||
Some(builder.build_load(ty, ptr, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::Store(target, value) => 'val: {
|
||||
let Some(t_val) = self.get_value(&values, target) else {
|
||||
break 'val None;
|
||||
};
|
||||
let Some(v_val) = self.get_value(&values, value) else {
|
||||
break 'val None;
|
||||
};
|
||||
let t_val = t_val.into_pointer_value();
|
||||
builder.build_store(t_val, v_val).unwrap();
|
||||
None
|
||||
}
|
||||
InstructionVariant::GetElementPtr(ptr, idx) => 'val: {
|
||||
let pointee_ty = self.get_type(match inst.value_ty() {
|
||||
Type::Ptr(PtrT { base, .. }) => *base,
|
||||
Type::Ref(RefT { base, .. }) => *base,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let Ok(pointee_ty): Result<BasicTypeEnum, _> = pointee_ty.try_into()
|
||||
else {
|
||||
break 'val None;
|
||||
};
|
||||
let ptr = self.get_value(&values, ptr).unwrap().into_pointer_value();
|
||||
let idx = self.get_value(&values, idx).unwrap().into_int_value();
|
||||
unsafe {
|
||||
let ptr = builder.build_gep(pointee_ty, ptr, &[idx], "").unwrap();
|
||||
Some(ptr.into())
|
||||
}
|
||||
}
|
||||
|
||||
InstructionVariant::IAdd(lhs, rhs) => {
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_add(lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::ISub(lhs, rhs) => {
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_sub(lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::IMul(lhs, rhs) => {
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_mul(lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::IDiv(lhs, rhs) if is_signed(lhs.ty()) => {
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_signed_div(lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::IDiv(lhs, rhs) => {
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_unsigned_div(lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::IMod(lhs, rhs) if is_signed(lhs.ty()) => {
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_signed_rem(lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::IMod(lhs, rhs) => {
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_unsigned_rem(lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::Trunc(val, target) => {
|
||||
let val = self.get_value(&values, val).unwrap().into_int_value();
|
||||
let target = self.get_type(Type::Int(*target)).into_int_type();
|
||||
Some(builder.build_int_truncate(val, target, "").unwrap().into())
|
||||
}
|
||||
InstructionVariant::ICmp(lhs, rhs, cmp) => {
|
||||
let u = !is_signed(lhs.ty());
|
||||
let cmp = match (cmp, u) {
|
||||
(Cmp::Eq, _) => IntPredicate::EQ,
|
||||
(Cmp::Ne, _) => IntPredicate::NE,
|
||||
(Cmp::Lt, true) => IntPredicate::ULT,
|
||||
(Cmp::Le, true) => IntPredicate::ULE,
|
||||
(Cmp::Gt, true) => IntPredicate::UGT,
|
||||
(Cmp::Ge, true) => IntPredicate::UGE,
|
||||
(Cmp::Lt, false) => IntPredicate::SLT,
|
||||
(Cmp::Le, false) => IntPredicate::SLE,
|
||||
(Cmp::Gt, false) => IntPredicate::SGT,
|
||||
(Cmp::Ge, false) => IntPredicate::SGE,
|
||||
};
|
||||
let lhs = self.get_value(&values, lhs).unwrap().into_int_value();
|
||||
let rhs = self.get_value(&values, rhs).unwrap().into_int_value();
|
||||
Some(builder.build_int_compare(cmp, lhs, rhs, "").unwrap().into())
|
||||
}
|
||||
|
||||
InstructionVariant::Call(func, args) => {
|
||||
// TODO This will fail with external assemblies. Fix this.
|
||||
let func = self.functions.get_sync(&(assembly, *func)).unwrap().clone();
|
||||
let args: Vec<_> = args
|
||||
.iter()
|
||||
.filter_map(|a| self.get_value(&values, a).map(|a| a.into()))
|
||||
.collect();
|
||||
|
||||
let value = builder.build_direct_call(func, &args, "").unwrap();
|
||||
value.as_any_value_enum().try_into().ok()
|
||||
}
|
||||
InstructionVariant::Jump(block) => {
|
||||
builder
|
||||
.build_unconditional_branch(blocks[block.id as usize])
|
||||
.unwrap();
|
||||
None
|
||||
}
|
||||
InstructionVariant::Branch {
|
||||
cond,
|
||||
true_case,
|
||||
false_case,
|
||||
} => {
|
||||
let cond = self.get_value(&values, cond).unwrap().into_int_value();
|
||||
builder
|
||||
.build_conditional_branch(
|
||||
cond,
|
||||
blocks[true_case.id as usize],
|
||||
blocks[false_case.id as usize],
|
||||
)
|
||||
.unwrap();
|
||||
None
|
||||
}
|
||||
|
||||
InstructionVariant::Return(None) => {
|
||||
builder.build_return(None).unwrap();
|
||||
None
|
||||
}
|
||||
|
||||
InstructionVariant::Return(Some(v)) => {
|
||||
let v = self.get_value(&values, v);
|
||||
builder
|
||||
.build_return(v.as_ref().map(|v| v as &dyn BasicValue))
|
||||
.unwrap();
|
||||
None
|
||||
}
|
||||
|
||||
InstructionVariant::Reinterpret(v, t, _) => match (v.ty(), t) {
|
||||
(Type::Ptr(_), Type::Ptr(_)) => {
|
||||
Some(self.get_value(&values, v).unwrap())
|
||||
}
|
||||
_ => todo!("{inst:?}"),
|
||||
},
|
||||
|
||||
_ => {
|
||||
module.print_to_stderr();
|
||||
todo!("{inst:?}");
|
||||
}
|
||||
};
|
||||
values.insert((*inst).into(), llvm_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module
|
||||
}
|
||||
|
||||
fn get_type(&self, ty: Type<'l>) -> AnyTypeEnum<'l> {
|
||||
if let Some(ty) = self.types.get_sync(&ty) {
|
||||
return *ty;
|
||||
}
|
||||
|
||||
let llvm_ty = match ty {
|
||||
Type::Void => self.ctx.void_type().into(),
|
||||
Type::I8 | Type::U8 => self.ctx.i8_type().into(),
|
||||
Type::I16 | Type::U16 => self.ctx.i16_type().into(),
|
||||
Type::I32 | Type::U32 => self.ctx.i32_type().into(),
|
||||
Type::I64 | Type::U64 => self.ctx.i64_type().into(),
|
||||
Type::I128 | Type::U128 => self.ctx.i128_type().into(),
|
||||
Type::USIZE | Type::ISIZE => self.native_int_ty.into(),
|
||||
|
||||
Type::Ptr(_) => self.ctx.ptr_type(AddressSpace::default()).into(),
|
||||
|
||||
Type::Func(FuncT { ret_t, par_t, .. }) => {
|
||||
let ret_t = self.get_type(*ret_t);
|
||||
let par_t = par_t
|
||||
.iter()
|
||||
.map(|ty| self.get_type(*ty))
|
||||
.filter_map(|ty| BasicMetadataTypeEnum::try_from(ty).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match ret_t {
|
||||
AnyTypeEnum::VoidType(ty) => ty.fn_type(&par_t, false).into(),
|
||||
AnyTypeEnum::IntType(ty) => ty.fn_type(&par_t, false).into(),
|
||||
_ => todo!("{ret_t:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Type::Array(ArrayT {
|
||||
base,
|
||||
length: Some(length),
|
||||
..
|
||||
}) => {
|
||||
let base = self.get_type(*base);
|
||||
match base {
|
||||
AnyTypeEnum::IntType(t) => t.array_type(*length).into(),
|
||||
AnyTypeEnum::FloatType(t) => t.array_type(*length).into(),
|
||||
AnyTypeEnum::StructType(t) => t.array_type(*length).into(),
|
||||
_ => todo!("{ty:#?}"),
|
||||
}
|
||||
}
|
||||
_ => todo!("{ty:#?}"),
|
||||
};
|
||||
self.types.entry_sync(ty).or_insert(llvm_ty);
|
||||
llvm_ty
|
||||
}
|
||||
|
||||
fn get_value(
|
||||
&self,
|
||||
map: &FxHashMap<Value<'l>, Option<BasicValueEnum<'l>>>,
|
||||
val: &Value<'l>,
|
||||
) -> Option<BasicValueEnum<'l>> {
|
||||
if let Some(value) = map.get(val) {
|
||||
return value.clone();
|
||||
}
|
||||
match val {
|
||||
Value::Constant(val) => match val {
|
||||
AnyConst::Int(Int::U8(v)) => {
|
||||
Some(self.ctx.i8_type().const_int(*v as u64, false).into())
|
||||
}
|
||||
AnyConst::Int(Int::U16(v)) => {
|
||||
Some(self.ctx.i16_type().const_int(*v as u64, false).into())
|
||||
}
|
||||
AnyConst::Int(Int::U32(v)) => {
|
||||
Some(self.ctx.i32_type().const_int(*v as u64, false).into())
|
||||
}
|
||||
AnyConst::Int(Int::U64(v)) => {
|
||||
Some(self.ctx.i64_type().const_int(*v as u64, false).into())
|
||||
}
|
||||
AnyConst::Int(Int::USize(v)) => {
|
||||
Some(self.native_int_ty.const_int(*v as u64, false).into())
|
||||
}
|
||||
AnyConst::Array([]) => todo!("{val:?}"),
|
||||
AnyConst::Array(array) => {
|
||||
let ty = self.get_type(array[0].ty());
|
||||
match ty {
|
||||
AnyTypeEnum::IntType(t) => {
|
||||
let mut values = vec![];
|
||||
for v in *array {
|
||||
let Some(BasicValueEnum::IntValue(v)) =
|
||||
self.get_value(map, &Value::Constant(*v))
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
values.push(v);
|
||||
}
|
||||
Some(t.const_array(&values).into())
|
||||
}
|
||||
_ => todo!("{ty:?}"),
|
||||
}
|
||||
}
|
||||
_ => todo!("{val:?}"),
|
||||
},
|
||||
_ => unreachable!("{val:#?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_signed(ty: Type) -> bool {
|
||||
match ty {
|
||||
Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::U128 | Type::USIZE => false,
|
||||
Type::I8 | Type::I16 | Type::I32 | Type::I64 | Type::I128 | Type::ISIZE => true,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user