use crate::encode::encode;
use crate::error::AotError;
use alloc::string::String;
use alloc::vec::Vec;
use codegen_lang::{Program, compile as lower};
use ir_lang::Function;
use linker_lang::{Image, Linker, Object};
const TEXT: &str = ".text";
fn object_from_program(program: &Program) -> Object {
let mut object = Object::new(program.name());
object.section(TEXT, encode(program));
object.define(program.name(), TEXT, 0);
object
}
pub fn compile(func: &Function) -> Result<Image, AotError> {
let program = lower(func)?;
let image = Linker::new()
.entry(program.name())
.link(&[object_from_program(&program)])?;
Ok(image)
}
#[derive(Default)]
pub struct Compiler {
objects: Vec<Object>,
base_address: u64,
entry: Option<String>,
}
impl Compiler {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn base_address(&mut self, addr: u64) -> &mut Self {
self.base_address = addr;
self
}
pub fn entry(&mut self, symbol: impl Into<String>) -> &mut Self {
self.entry = Some(symbol.into());
self
}
pub fn add(&mut self, func: &Function) -> Result<&mut Self, AotError> {
let program = lower(func)?;
self.objects.push(object_from_program(&program));
Ok(self)
}
pub fn link(&self) -> Result<Image, AotError> {
let mut linker = Linker::new().base_address(self.base_address);
if let Some(entry) = &self.entry {
linker = linker.entry(entry);
}
linker.link(&self.objects).map_err(AotError::from)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use crate::AotError;
use ir_lang::{BinOp, Builder, Type};
use linker_lang::LinkError;
fn unit_fn(name: &str) -> Function {
let mut b = Builder::new(name, &[], Type::Unit);
b.ret(None);
b.finish()
}
#[test]
fn test_compile_single_sets_entry_to_the_function() {
let mut b = Builder::new("inc", &[Type::Int], Type::Int);
let x = b.block_params(b.entry())[0];
let one = b.iconst(1);
let y = b.bin(BinOp::Add, x, one);
b.ret(Some(y));
let image = compile(&b.finish()).unwrap();
assert_eq!(image.entry(), Some(0));
assert_eq!(image.symbol("inc"), Some(0));
assert!(!image.section(".text").unwrap().data().is_empty());
}
#[test]
fn test_compile_rejects_invalid_function() {
let func = Builder::new("bad", &[], Type::Int).finish();
assert!(matches!(compile(&func), Err(AotError::Codegen(_))));
}
#[test]
fn test_compiler_lays_out_functions_in_add_order() {
let mut compiler = Compiler::new();
compiler.base_address(0x2000).entry("first");
compiler.add(&unit_fn("first")).unwrap();
compiler.add(&unit_fn("second")).unwrap();
let image = compiler.link().unwrap();
assert_eq!(image.entry(), Some(0x2000));
assert_eq!(image.symbol("first"), Some(0x2000));
assert!(image.symbol("second").unwrap() > 0x2000);
}
#[test]
fn test_compiler_duplicate_name_is_link_error() {
let mut compiler = Compiler::new();
compiler.add(&unit_fn("dup")).unwrap();
compiler.add(&unit_fn("dup")).unwrap();
match compiler.link() {
Err(AotError::Link(LinkError::DuplicateSymbol { name })) => assert_eq!(name, "dup"),
other => panic!("expected duplicate-symbol link error, got {other:?}"),
}
}
#[test]
fn test_compiler_undefined_entry_is_link_error() {
let mut compiler = Compiler::new();
compiler.entry("missing");
compiler.add(&unit_fn("present")).unwrap();
assert!(matches!(
compiler.link(),
Err(AotError::Link(LinkError::UndefinedEntry { .. }))
));
}
#[test]
fn test_compiler_default_has_no_entry() {
let mut compiler = Compiler::default();
compiler.add(&unit_fn("f")).unwrap();
assert_eq!(compiler.link().unwrap().entry(), None);
}
}