use crate::{CodegenError, CodegenOutputType, CodegenResult};
use bhc_session::OptLevel;
use inkwell::builder::Builder;
use inkwell::module::Module;
use inkwell::passes::PassBuilderOptions;
use inkwell::targets::FileType;
use inkwell::types::FunctionType;
use inkwell::values::{FunctionValue, PointerValue};
use inkwell::AddressSpace;
use std::path::Path;
use super::context::LlvmContext;
use super::types::TypeMapper;
pub struct LlvmModule<'ctx> {
module: Module<'ctx>,
builder: Builder<'ctx>,
type_mapper: TypeMapper<'ctx>,
name: String,
}
impl<'ctx> LlvmModule<'ctx> {
pub fn new(ctx: &'ctx LlvmContext, name: &str) -> CodegenResult<Self> {
let llvm_ctx = ctx.llvm_context();
let module = llvm_ctx.create_module(name);
module.set_triple(&ctx.target_machine().get_triple());
module.set_data_layout(&ctx.target_machine().get_target_data().get_data_layout());
let builder = llvm_ctx.create_builder();
let type_mapper = TypeMapper::new(llvm_ctx);
Ok(Self {
module,
builder,
type_mapper,
name: name.to_string(),
})
}
#[must_use]
pub fn llvm_module(&self) -> &Module<'ctx> {
&self.module
}
#[must_use]
pub fn builder(&self) -> &Builder<'ctx> {
&self.builder
}
#[must_use]
pub fn type_mapper(&self) -> &TypeMapper<'ctx> {
&self.type_mapper
}
pub fn add_function(&self, name: &str, fn_type: FunctionType<'ctx>) -> FunctionValue<'ctx> {
self.module.add_function(name, fn_type, None)
}
#[must_use]
pub fn get_function(&self, name: &str) -> Option<FunctionValue<'ctx>> {
self.module.get_function(name)
}
pub fn add_global_string(&self, name: &str, value: &str) -> PointerValue<'ctx> {
let string_val = self
.builder
.build_global_string_ptr(value, name)
.expect("failed to build global string");
string_val.as_pointer_value()
}
pub fn create_entry_point(
&self,
haskell_main: FunctionValue<'ctx>,
) -> CodegenResult<FunctionValue<'ctx>> {
let hs_name = haskell_main.get_name().to_str().unwrap_or("");
if hs_name == "main" {
haskell_main
.as_global_value()
.set_name("__bhc_haskell_main");
}
let i32_type = self.type_mapper.i32_type();
let void_type = self.type_mapper.context().void_type();
let ptr_type = self.type_mapper.context().ptr_type(AddressSpace::default());
let main_type = i32_type.fn_type(&[i32_type.into(), ptr_type.into()], false);
let main_fn = self.add_function("main", main_type);
let rts_init_type = void_type.fn_type(&[i32_type.into(), ptr_type.into()], false);
let rts_init = self
.module
.add_function("bhc_rts_init", rts_init_type, None);
let shutdown_type = void_type.fn_type(&[], false);
let shutdown = self
.module
.add_function("bhc_shutdown", shutdown_type, None);
let entry = self
.type_mapper
.context()
.append_basic_block(main_fn, "entry");
self.builder.position_at_end(entry);
let argc = main_fn
.get_nth_param(0)
.ok_or_else(|| CodegenError::Internal("missing argc param".to_string()))?;
let argv = main_fn
.get_nth_param(1)
.ok_or_else(|| CodegenError::Internal("missing argv param".to_string()))?;
self.builder
.build_call(rts_init, &[argc.into(), argv.into()], "")
.map_err(|e| {
CodegenError::Internal(format!("failed to build rts_init call: {:?}", e))
})?;
let null_env = ptr_type.const_null();
self.builder
.build_call(haskell_main, &[null_env.into()], "")
.map_err(|e| CodegenError::Internal(format!("failed to build call: {:?}", e)))?;
self.builder.build_call(shutdown, &[], "").map_err(|e| {
CodegenError::Internal(format!("failed to build shutdown call: {:?}", e))
})?;
let zero = i32_type.const_int(0, false);
self.builder
.build_return(Some(&zero))
.map_err(|e| CodegenError::Internal(format!("failed to build return: {:?}", e)))?;
Ok(main_fn)
}
fn emit_to_file(
&self,
ctx: &LlvmContext,
path: &Path,
file_type: FileType,
) -> CodegenResult<()> {
ctx.target_machine()
.write_to_file(&self.module, file_type, path)
.map_err(|e| CodegenError::OutputError {
path: path.display().to_string(),
source: std::io::Error::other(e.to_string()),
})
}
}
impl<'ctx> LlvmModule<'ctx> {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
pub fn verify(&self) -> CodegenResult<()> {
self.module.verify().map_err(|e| {
let err_str = e.to_string();
{
eprintln!("=== LLVM verification debug: scanning functions ===");
let mut fn_val = self.module.get_first_function();
while let Some(f) = fn_val {
let _name = f.get_name().to_str().unwrap_or("<unknown>");
if f.count_basic_blocks() > 0 {
let _bb_count = f.count_basic_blocks();
}
fn_val = f.get_next_function();
}
let _ = self.module.print_to_file("/tmp/bhc_debug.ll");
eprintln!("=== LLVM IR written to /tmp/bhc_debug.ll ===");
}
CodegenError::Internal(format!("LLVM verification failed: {}", err_str))
})
}
pub fn optimize(&mut self, ctx: &LlvmContext, level: OptLevel) -> CodegenResult<()> {
let passes = match level {
OptLevel::None => return Ok(()),
OptLevel::Less | OptLevel::Size | OptLevel::SizeMin => "default<O1>",
OptLevel::Default => "default<O2>",
OptLevel::Aggressive => "default<O3>",
};
let options = PassBuilderOptions::create();
self.module
.run_passes(passes, ctx.target_machine(), options)
.map_err(|e| CodegenError::Internal(format!("optimization failed: {}", e.to_string())))
}
pub fn write_to_file(&self, path: &Path, output_type: CodegenOutputType) -> CodegenResult<()> {
match output_type {
CodegenOutputType::LlvmIr => {
self.module
.print_to_file(path)
.map_err(|e| CodegenError::OutputError {
path: path.display().to_string(),
source: std::io::Error::other(e.to_string()),
})
}
CodegenOutputType::LlvmBitcode => {
if self.module.write_bitcode_to_path(path) {
Ok(())
} else {
Err(CodegenError::OutputError {
path: path.display().to_string(),
source: std::io::Error::other("failed to write bitcode"),
})
}
}
CodegenOutputType::Assembly | CodegenOutputType::Object => {
Err(CodegenError::Internal(
"object/assembly emission requires target machine - use emit_object or emit_assembly".to_string(),
))
}
}
}
#[must_use]
pub fn as_llvm_ir(&self) -> String {
self.module.print_to_string().to_string()
}
}
pub trait LlvmModuleExt<'ctx> {
fn emit_object(&self, ctx: &LlvmContext, path: &Path) -> CodegenResult<()>;
fn emit_assembly(&self, ctx: &LlvmContext, path: &Path) -> CodegenResult<()>;
}
impl<'ctx> LlvmModuleExt<'ctx> for LlvmModule<'ctx> {
fn emit_object(&self, ctx: &LlvmContext, path: &Path) -> CodegenResult<()> {
self.emit_to_file(ctx, path, FileType::Object)
}
fn emit_assembly(&self, ctx: &LlvmContext, path: &Path) -> CodegenResult<()> {
self.emit_to_file(ctx, path, FileType::Assembly)
}
}