use std::fmt;
use std::time::Instant;
use super::backend::{BackendOutput, CodegenBackend, NullBackend, select_backend};
use super::core::{IrBasicBlock, IrFunction, IrInstruction, IrModule, IrOpcode, IrType};
use super::metadata::{ComponentMetadata, ComponentSpec, OptimizationLevel};
use super::passes::PassManager;
#[derive(Debug, Clone)]
pub enum CompileError {
Frontend(String),
TypeCheck(String),
Lowering(String),
Pass(String),
Backend(String),
}
impl fmt::Display for CompileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CompileError::Frontend(msg) => write!(f, "frontend error: {msg}"),
CompileError::TypeCheck(msg) => write!(f, "type check error: {msg}"),
CompileError::Lowering(msg) => write!(f, "lowering error: {msg}"),
CompileError::Pass(msg) => write!(f, "pass error: {msg}"),
CompileError::Backend(msg) => write!(f, "backend error: {msg}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
Frontend,
TypeCheck,
Lower,
Optimize,
Codegen,
}
impl Stage {
pub fn label(&self) -> &'static str {
match self {
Stage::Frontend => "frontend",
Stage::TypeCheck => "typecheck",
Stage::Lower => "lower",
Stage::Optimize => "optimize",
Stage::Codegen => "codegen",
}
}
}
#[derive(Debug, Clone)]
pub struct StageTime {
pub stage: Stage,
pub elapsed_us: u64,
}
impl Default for StageTime {
fn default() -> Self {
Self {
stage: Stage::Frontend,
elapsed_us: 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CompileTimings {
pub stages: Vec<StageTime>,
pub total_us: u64,
}
#[derive(Debug)]
pub struct CompileResult {
pub module: IrModule,
pub output: BackendOutput,
pub metadata: ComponentMetadata,
pub timings: CompileTimings,
}
pub struct Compiler {
config: super::metadata::ComponentSpec,
pass_manager: PassManager,
backend: Box<dyn CodegenBackend>,
timings: CompileTimings,
}
impl Compiler {
pub fn new(spec: ComponentSpec) -> Result<Self, CompileError> {
let pass_manager = match spec.optimization_level {
OptimizationLevel::None => PassManager::new(),
OptimizationLevel::Less => PassManager::with_standard_passes(),
OptimizationLevel::Default => PassManager::with_standard_passes(),
OptimizationLevel::Aggressive => PassManager::with_aggressive_passes(),
};
let _kind = select_backend(&spec).map_err(|e| CompileError::Backend(format!("{e}")))?;
let backend: Box<dyn CodegenBackend> = Box::new(NullBackend);
Ok(Self {
config: spec,
pass_manager,
backend,
timings: CompileTimings::default(),
})
}
pub fn parse_source(&mut self, source: &str) -> Result<IrModule, CompileError> {
let start = Instant::now();
let mut module = IrModule::new(&self.config.name);
let entry_name = self
.config
.entry_point
.clone()
.unwrap_or_else(|| "main".to_string());
match crate::in_lang_parse::parse_in_source(source) {
Ok(unified) => {
let lowered = lower_unified_to_ir(&unified, &entry_name)?;
module = lowered;
}
Err(_e) => {
let mut func = IrFunction::new(&entry_name, vec![], IrType::Void);
let mut block = IrBasicBlock::new("entry");
block.terminator = Some(IrInstruction::new(IrOpcode::Return, IrType::Void, vec![]));
func.add_block(block);
module.functions.push(func);
}
}
module.component = Some(self.config.clone());
self.timings.stages.push(StageTime {
stage: Stage::Frontend,
elapsed_us: start.elapsed().as_micros() as u64,
});
Ok(module)
}
pub fn type_check(&mut self, _module: &mut IrModule) -> Result<(), CompileError> {
let start = Instant::now();
self.timings.stages.push(StageTime {
stage: Stage::TypeCheck,
elapsed_us: start.elapsed().as_micros() as u64,
});
Ok(())
}
pub fn lower(&mut self, _module: &mut IrModule) -> Result<(), CompileError> {
let start = Instant::now();
self.timings.stages.push(StageTime {
stage: Stage::Lower,
elapsed_us: start.elapsed().as_micros() as u64,
});
Ok(())
}
pub fn optimize(&mut self, module: &mut IrModule) -> Result<(), CompileError> {
let start = Instant::now();
self.pass_manager
.run_all(module)
.map_err(|e| CompileError::Pass(e.0))?;
self.timings.stages.push(StageTime {
stage: Stage::Optimize,
elapsed_us: start.elapsed().as_micros() as u64,
});
Ok(())
}
pub fn codegen(&mut self, module: &IrModule) -> Result<BackendOutput, CompileError> {
let start = Instant::now();
let output = self
.backend
.emit(module, &self.config)
.map_err(|e| CompileError::Backend(format!("{e:?}")))?;
self.timings.stages.push(StageTime {
stage: Stage::Codegen,
elapsed_us: start.elapsed().as_micros() as u64,
});
Ok(output)
}
pub fn compile(&mut self, source: &str) -> Result<CompileResult, CompileError> {
let total_start = Instant::now();
let mut module = self.parse_source(source)?;
self.type_check(&mut module)?;
self.lower(&mut module)?;
self.optimize(&mut module)?;
let output = self.codegen(&module)?;
let metadata = ComponentMetadata::build(&self.config, &module, &output, source);
self.timings.total_us = total_start.elapsed().as_micros() as u64;
Ok(CompileResult {
module,
output,
metadata,
timings: self.timings.clone(),
})
}
pub fn print_timings(&self) {
println!(" Compiler pipeline stages:");
for stage_time in &self.timings.stages {
println!(
" {:12} {:.3}ms",
stage_time.stage.label(),
(stage_time.elapsed_us as f64) / 1000.0
);
}
println!(
" {:12} {:.3}ms",
"total",
(self.timings.total_us as f64) / 1000.0
);
}
}
fn lower_unified_to_ir(
unified: &crate::core_ir::UnifiedModule,
entry_name: &str,
) -> Result<IrModule, CompileError> {
let mut module = IrModule::new(unified.identity.module.as_deref().unwrap_or("module"));
for decl in &unified.decls {
if let crate::core_ir::Decl::Struct { name, fields, .. } = decl {
let ir_fields: Vec<(String, IrType)> = fields
.iter()
.map(|(n, t)| (n.clone(), lower_typ(t)))
.collect();
module.struct_types.push((name.clone(), ir_fields));
}
}
for decl in &unified.decls {
if let crate::core_ir::Decl::Function {
name,
params,
ret,
body,
..
} = decl
{
let ir_params: Vec<(String, IrType)> = params
.iter()
.map(|(n, t)| (n.clone(), lower_typ(t)))
.collect();
let ir_ret = lower_typ(ret);
let ir_ret_for_block = ir_ret.clone();
let mut func = IrFunction::new(name, ir_params, ir_ret);
let mut block = IrBasicBlock::new("entry");
let has_return = body
.iter()
.any(|s| matches!(s, crate::core_ir::Stmt::Return(_)));
if !has_return && ir_ret_for_block == IrType::Void {
block.terminator = Some(IrInstruction::new(IrOpcode::Return, IrType::Void, vec![]));
} else {
block.terminator = Some(IrInstruction::new(
IrOpcode::Return,
ir_ret_for_block,
vec![],
));
}
func.add_block(block);
module.functions.push(func);
}
}
if module.functions.is_empty() {
let mut func = IrFunction::new(entry_name, vec![], IrType::Void);
let mut block = IrBasicBlock::new("entry");
block.terminator = Some(IrInstruction::new(IrOpcode::Return, IrType::Void, vec![]));
func.add_block(block);
module.functions.push(func);
}
Ok(module)
}
fn lower_typ(typ: &crate::core_ir::Typ) -> IrType {
match typ {
crate::core_ir::Typ::Int => IrType::I64,
crate::core_ir::Typ::Bool => IrType::Bool,
crate::core_ir::Typ::String => IrType::Slice(Box::new(IrType::I8)),
crate::core_ir::Typ::Float => IrType::F64,
crate::core_ir::Typ::Void => IrType::Void,
crate::core_ir::Typ::Array(elem) => IrType::Array(Box::new(lower_typ(elem)), 0),
crate::core_ir::Typ::Named(name) => IrType::Named(name.clone()),
crate::core_ir::Typ::Generic(name) => IrType::Named(name.clone()),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_spec() -> ComponentSpec {
ComponentSpec::host_executable("test_app", Some("main"))
}
#[test]
fn compiler_creates_from_spec() {
let compiler = Compiler::new(test_spec()).unwrap();
assert!(
compiler.backend.kind() == crate::compiler::backend::BackendKind::AArch64MachO
|| compiler.backend.kind() == crate::compiler::backend::BackendKind::RawBinary,
);
}
#[test]
fn compiler_runs_full_pipeline() {
let mut compiler = Compiler::new(test_spec()).unwrap();
let result = compiler.compile("fn main() {}").unwrap();
assert!(!result.output.data.is_empty());
assert_eq!(result.timings.stages.len(), 5);
}
#[test]
fn compiler_parses_in_source() {
let mut compiler = Compiler::new(test_spec()).unwrap();
let source = "fn greet() -> Int { return 42; }";
let module = compiler.parse_source(source).unwrap();
assert_eq!(module.functions.len(), 1);
}
#[test]
fn compiler_returns_timing_report() {
let mut compiler = Compiler::new(test_spec()).unwrap();
let _result = compiler.compile("fn main() {}").unwrap();
compiler.print_timings();
}
#[test]
fn lower_unified_module_produces_ir() {
let source = "fn main() {}\nfn add(a: Int, b: Int) -> Int { return a + b; }";
let unified = crate::in_lang_parse::parse_in_source(source).unwrap();
let module = lower_unified_to_ir(&unified, "main").unwrap();
assert!(module.functions.len() >= 1);
assert_eq!(module.functions[0].name, "main");
}
}