feat: add a backed using cranelift ir and codegen crates
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use cranelift_codegen::{
|
||||
Context,
|
||||
control::ControlPlane,
|
||||
ir::{self, AbiParam, InstBuilder, types},
|
||||
settings::{self, Configurable},
|
||||
};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
|
||||
use cranelift_module::{Linkage, Module, default_libcall_names};
|
||||
use cranelift_object::{ObjectBuilder, ObjectModule};
|
||||
|
||||
use crate::frontend::{
|
||||
ast::{BinaryOp, UnaryOp},
|
||||
sema::Ty,
|
||||
typed_ast::*,
|
||||
};
|
||||
|
||||
/// The backend responsible for lowering a `TypedModule` into Cranelift IR and
|
||||
/// generating native machine code object files.
|
||||
pub struct CraneliftBackend {
|
||||
ctx: Context,
|
||||
builder_context: FunctionBuilderContext,
|
||||
module: ObjectModule,
|
||||
}
|
||||
|
||||
impl CraneliftBackend {
|
||||
/// Initializes the Cranelift backend with the host's native instruction set architecture (ISA),
|
||||
/// enabling speed optimizations and position-independent code (PIC) generation.
|
||||
pub fn new() -> Self {
|
||||
let mut flag_builder = settings::builder();
|
||||
flag_builder.set("is_pic", "true").unwrap();
|
||||
flag_builder.set("opt_level", "speed").unwrap();
|
||||
|
||||
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
|
||||
panic!("host machine is not supported: {}", msg);
|
||||
});
|
||||
|
||||
let isa = isa_builder
|
||||
.finish(settings::Flags::new(flag_builder))
|
||||
.unwrap();
|
||||
|
||||
let builder = ObjectBuilder::new(isa, "module", default_libcall_names()).unwrap();
|
||||
|
||||
Self {
|
||||
ctx: Context::new(),
|
||||
builder_context: FunctionBuilderContext::new(),
|
||||
module: ObjectModule::new(builder),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiles a fully typed AST module into native object code.
|
||||
///
|
||||
/// Returns a tuple containing the generated Cranelift IR (as a human-readable string) and the assembled object file bytes.
|
||||
pub fn compile_module(mut self, module: &TypedModule) -> (String, Vec<u8>) {
|
||||
let mut ir_output = String::new();
|
||||
|
||||
for decl in &module.decls {
|
||||
match decl {
|
||||
TypedDecl::Function {
|
||||
name,
|
||||
params,
|
||||
return_type,
|
||||
body,
|
||||
} => {
|
||||
self.compile_function(params, return_type, body);
|
||||
|
||||
// Run Cranelift's optimization passes before emitting the text IR
|
||||
let mut ctrl_plane = ControlPlane::default();
|
||||
self.ctx
|
||||
.optimize(self.module.isa(), &mut ctrl_plane)
|
||||
.unwrap();
|
||||
|
||||
ir_output.push_str(&format!(
|
||||
"; Function: {}\n{}",
|
||||
name,
|
||||
self.ctx.func.to_string()
|
||||
));
|
||||
ir_output.push('\n');
|
||||
|
||||
let func_id = self
|
||||
.module
|
||||
.declare_function(name, Linkage::Export, &self.ctx.func.signature)
|
||||
.unwrap();
|
||||
|
||||
self.module.define_function(func_id, &mut self.ctx).unwrap();
|
||||
self.module.clear_context(&mut self.ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let obj_bytes = self.module.finish().emit().unwrap();
|
||||
(ir_output, obj_bytes)
|
||||
}
|
||||
|
||||
/// Lowers a single function declaration into Cranelift IR.
|
||||
///
|
||||
/// This sets up the function signature, ABI parameters, entry block, and declares the parameters as local variables.
|
||||
fn compile_function(&mut self, params: &[(String, Ty)], return_type: &Ty, body: &TypedStmt) {
|
||||
let mut sig = self.module.make_signature();
|
||||
|
||||
for (_, ty) in params {
|
||||
sig.params.push(AbiParam::new(Self::lower_type(ty)));
|
||||
}
|
||||
|
||||
if return_type != &Ty::Unit {
|
||||
sig.returns
|
||||
.push(AbiParam::new(Self::lower_type(return_type)));
|
||||
}
|
||||
|
||||
self.ctx.func.signature = sig;
|
||||
self.ctx.func.name = ir::UserFuncName::user(0, 0);
|
||||
|
||||
let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);
|
||||
|
||||
let entry_block = builder.create_block();
|
||||
builder.append_block_params_for_function_params(entry_block);
|
||||
builder.switch_to_block(entry_block);
|
||||
builder.seal_block(entry_block);
|
||||
|
||||
let mut vars = HashMap::new();
|
||||
|
||||
for (i, (param_name, ty)) in params.iter().enumerate() {
|
||||
let var = builder.declare_var(Self::lower_type(ty));
|
||||
let val = builder.block_params(entry_block)[i];
|
||||
builder.def_var(var, val);
|
||||
vars.insert(param_name.clone(), var);
|
||||
}
|
||||
|
||||
let mut trans = FunctionTranslator { builder, vars };
|
||||
|
||||
trans.translate_stmt(body);
|
||||
trans.builder.finalize();
|
||||
}
|
||||
|
||||
/// Maps our semantic types (`Ty`) to Cranelift's internal types (`ir::Type`).
|
||||
fn lower_type(ty: &Ty) -> ir::Type {
|
||||
match ty {
|
||||
Ty::I8 | Ty::U8 => types::I8,
|
||||
Ty::I16 | Ty::U16 => types::I16,
|
||||
Ty::I32 | Ty::U32 => types::I32,
|
||||
Ty::I64 | Ty::U64 => types::I64,
|
||||
Ty::Bool => types::I8, // Booleans are represented as 8-bit integers
|
||||
_ => unimplemented!("Unsupported type for Cranelift lowering: {:?}", ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A visitor that traverses typed statements and expressions, emitting Cranelift IR instructions into the current function builder.
|
||||
struct FunctionTranslator<'a> {
|
||||
builder: FunctionBuilder<'a>,
|
||||
vars: HashMap<String, Variable>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionTranslator<'a> {
|
||||
/// Translates a statement, recursively compiling its inner components.
|
||||
fn translate_stmt(&mut self, stmt: &TypedStmt) {
|
||||
match stmt {
|
||||
TypedStmt::Compound { inner } => {
|
||||
for s in inner {
|
||||
self.translate_stmt(s);
|
||||
}
|
||||
}
|
||||
TypedStmt::Return { value } => {
|
||||
if let Some(expr) = value {
|
||||
let val = self.translate_expr(expr);
|
||||
self.builder.ins().return_(&[val]);
|
||||
} else {
|
||||
self.builder.ins().return_(&[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Translates an expression into a Cranelift IR value.
|
||||
/// Emits appropriate computation instructions based on operators and operand types.
|
||||
fn translate_expr(&mut self, expr: &TypedExpr) -> ir::Value {
|
||||
match &expr.kind {
|
||||
TypedExprKind::Identifier { name } => {
|
||||
let var = self.vars.get(name).expect("Undeclared variable");
|
||||
self.builder.use_var(*var)
|
||||
}
|
||||
TypedExprKind::Integer { value } => {
|
||||
let ty = CraneliftBackend::lower_type(&expr.ty);
|
||||
self.builder.ins().iconst(ty, *value as i64)
|
||||
}
|
||||
TypedExprKind::Boolean { value } => {
|
||||
let ty = CraneliftBackend::lower_type(&expr.ty);
|
||||
self.builder.ins().iconst(ty, if *value { 1 } else { 0 })
|
||||
}
|
||||
TypedExprKind::Unary { op, expr: inner } => {
|
||||
let inner_val = self.translate_expr(inner);
|
||||
match op {
|
||||
UnaryOp::Neg => self.builder.ins().ineg(inner_val),
|
||||
}
|
||||
}
|
||||
TypedExprKind::Binary { op, lhs, rhs } => {
|
||||
let lhs_val = self.translate_expr(lhs);
|
||||
let rhs_val = self.translate_expr(rhs);
|
||||
|
||||
let is_signed = matches!(lhs.ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64);
|
||||
|
||||
match op {
|
||||
BinaryOp::Add => self.builder.ins().iadd(lhs_val, rhs_val),
|
||||
BinaryOp::Sub => self.builder.ins().isub(lhs_val, rhs_val),
|
||||
BinaryOp::Mul => self.builder.ins().imul(lhs_val, rhs_val),
|
||||
BinaryOp::Div => {
|
||||
if is_signed {
|
||||
self.builder.ins().sdiv(lhs_val, rhs_val)
|
||||
} else {
|
||||
self.builder.ins().udiv(lhs_val, rhs_val)
|
||||
}
|
||||
}
|
||||
BinaryOp::Rem => {
|
||||
if is_signed {
|
||||
self.builder.ins().srem(lhs_val, rhs_val)
|
||||
} else {
|
||||
self.builder.ins().urem(lhs_val, rhs_val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod cranelift;
|
||||
+44
-8
@@ -1,17 +1,34 @@
|
||||
use std::{env::args, fs::read_to_string, process::exit};
|
||||
use std::{fs::read_to_string, path::PathBuf, process::exit};
|
||||
|
||||
use clap::Parser as ClapParser;
|
||||
|
||||
use crate::frontend::parser::Parser;
|
||||
use crate::frontend::sema::Sema;
|
||||
|
||||
pub mod backend;
|
||||
pub mod frontend;
|
||||
|
||||
fn main() {
|
||||
let Some(path) = args().nth(1) else {
|
||||
eprintln!("usage: compiler <file>");
|
||||
exit(1);
|
||||
};
|
||||
use crate::backend::cranelift::CraneliftBackend;
|
||||
|
||||
let content = read_to_string(&path).unwrap_or_else(|err| {
|
||||
#[derive(ClapParser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
/// The input source file to compile
|
||||
input: String,
|
||||
|
||||
/// The output file path
|
||||
#[arg(short, long)]
|
||||
output: Option<String>,
|
||||
|
||||
/// Emit Cranelift IR instead of an object file
|
||||
#[arg(long)]
|
||||
emit_ir: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let content = read_to_string(&cli.input).unwrap_or_else(|err| {
|
||||
eprintln!("error: failed to read source file ({:?})", err);
|
||||
exit(1);
|
||||
});
|
||||
@@ -38,5 +55,24 @@ fn main() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
println!("{:#?}", typed_module);
|
||||
let backend = CraneliftBackend::new();
|
||||
let (ir, obj_bytes) = backend.compile_module(&typed_module);
|
||||
|
||||
if cli.emit_ir {
|
||||
println!("{}", ir);
|
||||
} else {
|
||||
let output_path = cli.output.unwrap_or_else(|| {
|
||||
PathBuf::from(&cli.input)
|
||||
.with_extension("o")
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
});
|
||||
|
||||
std::fs::write(&output_path, obj_bytes).unwrap_or_else(|err| {
|
||||
eprintln!("error: failed to write object file: {:?}", err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
println!("Generated object file: {}", output_path);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user