#![warn(missing_docs)]
pub mod llvm;
use bhc_session::{DebugInfo, OptLevel, OutputType};
use bhc_target::TargetSpec;
use std::path::Path;
use thiserror::Error;
pub use llvm::{LlvmBackend, LlvmContext, LlvmModule};
#[derive(Debug, Error)]
pub enum CodegenError {
#[error("backend not available: {0}")]
BackendNotAvailable(String),
#[error("unsupported target: {0}")]
UnsupportedTarget(String),
#[error("LLVM error: {0}")]
LlvmError(String),
#[error("failed to write output: {path}")]
OutputError {
path: String,
#[source]
source: std::io::Error,
},
#[error("internal codegen error: {0}")]
Internal(String),
#[error("type error: {0}")]
TypeError(String),
#[error("unsupported: {0}")]
Unsupported(String),
}
pub type CodegenResult<T> = Result<T, CodegenError>;
#[derive(Clone, Debug)]
pub struct CodegenConfig {
pub target: TargetSpec,
pub opt_level: OptLevel,
pub debug_info: DebugInfo,
pub pic: bool,
pub frame_pointers: bool,
pub lto: bool,
pub cpu: String,
}
impl Default for CodegenConfig {
fn default() -> Self {
Self {
target: bhc_target::host_target(),
opt_level: OptLevel::Default,
debug_info: DebugInfo::None,
pic: false,
frame_pointers: true,
lto: false,
cpu: "generic".to_string(),
}
}
}
impl CodegenConfig {
#[must_use]
pub fn for_target(target: TargetSpec) -> Self {
Self {
target,
..Self::default()
}
}
#[must_use]
pub fn with_opt_level(mut self, level: OptLevel) -> Self {
self.opt_level = level;
self
}
#[must_use]
pub fn with_debug_info(mut self, level: DebugInfo) -> Self {
self.debug_info = level;
self
}
#[must_use]
pub fn with_pic(mut self, pic: bool) -> Self {
self.pic = pic;
self
}
#[must_use]
pub fn with_lto(mut self, lto: bool) -> Self {
self.lto = lto;
self
}
#[must_use]
pub fn with_cpu(mut self, cpu: impl Into<String>) -> Self {
self.cpu = cpu.into();
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CodegenOutputType {
Object,
Assembly,
LlvmIr,
LlvmBitcode,
}
impl From<OutputType> for CodegenOutputType {
fn from(value: OutputType) -> Self {
match value {
OutputType::Assembly => Self::Assembly,
OutputType::LlvmIr => Self::LlvmIr,
OutputType::LlvmBitcode => Self::LlvmBitcode,
_ => Self::Object,
}
}
}
pub trait CodegenModule {
fn name(&self) -> &str;
fn verify(&self) -> CodegenResult<()>;
fn optimize(&mut self, level: OptLevel) -> CodegenResult<()>;
fn write_to_file(&self, path: &Path, output_type: CodegenOutputType) -> CodegenResult<()>;
fn as_llvm_ir(&self) -> CodegenResult<String>;
}
pub trait CodegenContext: Send + Sync {
type Module: CodegenModule;
fn create_module(&self, name: &str) -> CodegenResult<Self::Module>;
fn target(&self) -> &TargetSpec;
fn config(&self) -> &CodegenConfig;
}
pub trait CodegenBackend: Send + Sync {
type Context: CodegenContext;
fn name(&self) -> &'static str;
fn supports_target(&self, target: &TargetSpec) -> bool;
fn create_context(&self, config: CodegenConfig) -> CodegenResult<Self::Context>;
}
#[derive(Clone, Debug)]
pub struct TypeLayout {
pub size: u64,
pub alignment: u64,
}
impl TypeLayout {
#[must_use]
pub fn pointer(target: &TargetSpec) -> Self {
let width = target.pointer_width() as u64;
Self {
size: width,
alignment: width,
}
}
#[must_use]
pub const fn i64() -> Self {
Self {
size: 8,
alignment: 8,
}
}
#[must_use]
pub const fn f64() -> Self {
Self {
size: 8,
alignment: 8,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_codegen_config() {
let config = CodegenConfig::default()
.with_opt_level(OptLevel::Aggressive)
.with_pic(true);
assert_eq!(config.opt_level, OptLevel::Aggressive);
assert!(config.pic);
}
#[test]
fn test_llvm_backend_creation() {
let backend = LlvmBackend::new();
assert_eq!(backend.name(), "llvm");
assert!(backend.is_available());
}
#[test]
fn test_llvm_context_creation() {
let backend = LlvmBackend::new();
let config = CodegenConfig::default();
let result = backend.create_context(config);
assert!(result.is_ok());
}
#[test]
fn test_module_creation() {
let ctx = LlvmContext::new(CodegenConfig::default()).unwrap();
let module = ctx.create_module("test").unwrap();
assert_eq!(module.name(), "test");
let ir = module.as_llvm_ir();
assert!(ir.contains("ModuleID"));
assert!(ir.contains("target triple"));
}
#[test]
fn test_module_verification() {
let ctx = LlvmContext::new(CodegenConfig::default()).unwrap();
let module = ctx.create_module("test").unwrap();
assert!(module.verify().is_ok());
}
}