feat: unify AST structures, introduce MIR and update codegen
This commit is contained in:
+154
-134
@@ -13,8 +13,8 @@ use cranelift_object::{ObjectBuilder, ObjectModule};
|
||||
use crate::frontend::{
|
||||
ast::{BinaryOp, UnaryOp},
|
||||
sema::Ty,
|
||||
typed_ast::*,
|
||||
};
|
||||
use crate::middle::mir::*;
|
||||
|
||||
/// The backend responsible for lowering a `TypedModule` into Cranelift IR and
|
||||
/// generating native machine code object files.
|
||||
@@ -49,63 +49,53 @@ impl CraneliftBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiles a fully typed AST module into native object code.
|
||||
/// Compiles a MIR 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>) {
|
||||
pub fn compile_module(mut self, module: &MirModule) -> (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);
|
||||
for func in &module.functions {
|
||||
self.compile_function(func);
|
||||
|
||||
// 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();
|
||||
// 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');
|
||||
ir_output.push_str(&format!(
|
||||
"; Function: {}\n{}",
|
||||
func.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();
|
||||
let func_id = self
|
||||
.module
|
||||
.declare_function(&func.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);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
/// Lowers a single MIR function into Cranelift IR.
|
||||
fn compile_function(&mut self, func: &MirFunction) {
|
||||
let mut sig = self.module.make_signature();
|
||||
|
||||
for (_, ty) in params {
|
||||
sig.params.push(AbiParam::new(Self::lower_type(ty)));
|
||||
for param_id in &func.params {
|
||||
let param_ty = &func.locals[param_id.0].ty;
|
||||
sig.params.push(AbiParam::new(Self::lower_type(param_ty)));
|
||||
}
|
||||
|
||||
if return_type != &Ty::Unit {
|
||||
if func.return_type != Ty::Unit {
|
||||
sig.returns
|
||||
.push(AbiParam::new(Self::lower_type(return_type)));
|
||||
.push(AbiParam::new(Self::lower_type(&func.return_type)));
|
||||
}
|
||||
|
||||
self.ctx.func.signature = sig;
|
||||
@@ -113,23 +103,55 @@ impl CraneliftBackend {
|
||||
|
||||
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 block_map = HashMap::new();
|
||||
for block in &func.blocks {
|
||||
block_map.insert(block.id, builder.create_block());
|
||||
}
|
||||
|
||||
let mut trans = FunctionTranslator { builder, vars };
|
||||
let mut var_map = HashMap::new();
|
||||
for local in &func.locals {
|
||||
let var = builder.declare_var(Self::lower_type(&local.ty));
|
||||
var_map.insert(local.id, var);
|
||||
}
|
||||
|
||||
let mut trans = FunctionTranslator {
|
||||
builder,
|
||||
var_map,
|
||||
block_map,
|
||||
locals: &func.locals,
|
||||
};
|
||||
|
||||
if let Some(first_block) = func.blocks.first() {
|
||||
let entry_block = trans.block_map[&first_block.id];
|
||||
trans
|
||||
.builder
|
||||
.append_block_params_for_function_params(entry_block);
|
||||
}
|
||||
|
||||
for (i, block) in func.blocks.iter().enumerate() {
|
||||
let cl_block = trans.block_map[&block.id];
|
||||
trans.builder.switch_to_block(cl_block);
|
||||
|
||||
// Retrieve function arguments explicitly if this is the entry block
|
||||
if i == 0 {
|
||||
for (j, param_id) in func.params.iter().enumerate() {
|
||||
let val = trans.builder.block_params(cl_block)[j];
|
||||
trans.builder.def_var(trans.var_map[param_id], val);
|
||||
}
|
||||
}
|
||||
|
||||
for stmt in &block.statements {
|
||||
trans.translate_stmt(stmt);
|
||||
}
|
||||
|
||||
trans.translate_terminator(&block.terminator);
|
||||
}
|
||||
|
||||
// Seal all blocks now that all branches are translated and predecessors are known
|
||||
for cl_block in trans.block_map.values() {
|
||||
trans.builder.seal_block(*cl_block);
|
||||
}
|
||||
|
||||
trans.translate_stmt(body);
|
||||
trans.builder.finalize();
|
||||
}
|
||||
|
||||
@@ -146,109 +168,107 @@ impl CraneliftBackend {
|
||||
}
|
||||
}
|
||||
|
||||
/// A visitor that traverses typed statements and expressions, emitting Cranelift IR instructions into the current function builder.
|
||||
/// A visitor that traverses MIR basic blocks and instructions, emitting Cranelift IR instructions
|
||||
/// into the current function builder.
|
||||
struct FunctionTranslator<'a> {
|
||||
builder: FunctionBuilder<'a>,
|
||||
vars: HashMap<String, Variable>,
|
||||
var_map: HashMap<LocalId, Variable>,
|
||||
block_map: HashMap<BlockId, ir::Block>,
|
||||
locals: &'a [LocalDecl],
|
||||
}
|
||||
|
||||
impl<'a> FunctionTranslator<'a> {
|
||||
/// Translates a statement, recursively compiling its inner components.
|
||||
/// Returns `true` if the statement resulted in a basic block terminator.
|
||||
fn translate_stmt(&mut self, stmt: &TypedStmt) -> bool {
|
||||
match stmt {
|
||||
TypedStmt::Compound { inner } => {
|
||||
for s in inner {
|
||||
if self.translate_stmt(s) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
TypedStmt::If {
|
||||
condition,
|
||||
then,
|
||||
elze,
|
||||
} => {
|
||||
let cond_val = self.translate_expr(condition);
|
||||
|
||||
let then_block = self.builder.create_block();
|
||||
let else_block = self.builder.create_block();
|
||||
let merge_block = self.builder.create_block();
|
||||
|
||||
self.builder
|
||||
.ins()
|
||||
.brif(cond_val, then_block, &[], else_block, &[]);
|
||||
|
||||
self.builder.switch_to_block(then_block);
|
||||
self.builder.seal_block(then_block);
|
||||
let then_terminated = self.translate_stmt(then);
|
||||
if !then_terminated {
|
||||
self.builder.ins().jump(merge_block, &[]);
|
||||
}
|
||||
|
||||
self.builder.switch_to_block(else_block);
|
||||
self.builder.seal_block(else_block);
|
||||
let else_terminated = elze
|
||||
.as_ref()
|
||||
.map(|stmt| self.translate_stmt(stmt))
|
||||
.unwrap_or(false);
|
||||
if !else_terminated {
|
||||
self.builder.ins().jump(merge_block, &[]);
|
||||
}
|
||||
|
||||
self.builder.switch_to_block(merge_block);
|
||||
self.builder.seal_block(merge_block);
|
||||
|
||||
then_terminated && else_terminated
|
||||
}
|
||||
TypedStmt::Return { value } => {
|
||||
if let Some(expr) = value {
|
||||
let val = self.translate_expr(expr);
|
||||
self.builder.ins().return_(&[val]);
|
||||
} else {
|
||||
self.builder.ins().return_(&[]);
|
||||
}
|
||||
true
|
||||
fn translate_stmt(&mut self, stmt: &Statement) {
|
||||
match &stmt.kind {
|
||||
StatementKind::Assign(local_id, rvalue) => {
|
||||
let val = self.translate_rvalue(rvalue);
|
||||
let var = self.var_map[local_id];
|
||||
self.builder.def_var(var, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
fn translate_terminator(&mut self, term: &Terminator) {
|
||||
match &term.kind {
|
||||
TerminatorKind::Goto { target } => {
|
||||
self.builder.ins().jump(self.block_map[target], &[]);
|
||||
}
|
||||
TypedExprKind::Integer { value } => {
|
||||
let ty = CraneliftBackend::lower_type(&expr.ty);
|
||||
self.builder.ins().iconst(ty, *value as i64)
|
||||
TerminatorKind::CondBranch {
|
||||
cond,
|
||||
target_true,
|
||||
target_false,
|
||||
} => {
|
||||
let cond_val = self.translate_operand(cond);
|
||||
self.builder.ins().brif(
|
||||
cond_val,
|
||||
self.block_map[target_true],
|
||||
&[],
|
||||
self.block_map[target_false],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
TypedExprKind::Boolean { value } => {
|
||||
let ty = CraneliftBackend::lower_type(&expr.ty);
|
||||
self.builder.ins().iconst(ty, if *value { 1 } else { 0 })
|
||||
TerminatorKind::Return { value } => {
|
||||
if let Some(op) = value {
|
||||
let val = self.translate_operand(op);
|
||||
self.builder.ins().return_(&[val]);
|
||||
} else {
|
||||
self.builder.ins().return_(&[]);
|
||||
}
|
||||
}
|
||||
TypedExprKind::Unary { op, expr: inner } => {
|
||||
let inner_val = self.translate_expr(inner);
|
||||
TerminatorKind::Unreachable => {
|
||||
self.builder.ins().trap(ir::TrapCode::user(5).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_operand_type(&self, op: &Operand) -> Ty {
|
||||
match op {
|
||||
Operand::Copy(local_id) => self.locals[local_id.0].ty.clone(),
|
||||
Operand::Constant(ConstantValue::Integer(_, ty)) => ty.clone(),
|
||||
Operand::Constant(ConstantValue::Boolean(_)) => Ty::Bool,
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_operand(&mut self, op: &Operand) -> ir::Value {
|
||||
match op {
|
||||
Operand::Copy(local_id) => {
|
||||
let var = self.var_map[local_id];
|
||||
self.builder.use_var(var)
|
||||
}
|
||||
Operand::Constant(ConstantValue::Integer(val, ty)) => {
|
||||
let cl_ty = CraneliftBackend::lower_type(ty);
|
||||
self.builder.ins().iconst(cl_ty, *val as i64)
|
||||
}
|
||||
Operand::Constant(ConstantValue::Boolean(val)) => self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(types::I8, if *val { 1 } else { 0 }),
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_rvalue(&mut self, rvalue: &Rvalue) -> ir::Value {
|
||||
match rvalue {
|
||||
Rvalue::Use(op) => self.translate_operand(op),
|
||||
Rvalue::UnaryOp(op, inner) => {
|
||||
let inner_val = self.translate_operand(inner);
|
||||
match op {
|
||||
UnaryOp::Neg => self.builder.ins().ineg(inner_val),
|
||||
UnaryOp::Not => {
|
||||
// `!x` is equivalent to `x == 0` for booleans (0 or 1).
|
||||
let ty = CraneliftBackend::lower_type(&inner.ty);
|
||||
let zero = self.builder.ins().iconst(ty, 0);
|
||||
let ty = self.get_operand_type(inner);
|
||||
let cl_ty = CraneliftBackend::lower_type(&ty);
|
||||
let zero = self.builder.ins().iconst(cl_ty, 0);
|
||||
self.builder
|
||||
.ins()
|
||||
.icmp(ir::condcodes::IntCC::Equal, inner_val, zero)
|
||||
}
|
||||
}
|
||||
}
|
||||
TypedExprKind::Binary { op, lhs, rhs } => {
|
||||
let lhs_val = self.translate_expr(lhs);
|
||||
let rhs_val = self.translate_expr(rhs);
|
||||
Rvalue::BinaryOp(op, lhs, rhs) => {
|
||||
let lhs_val = self.translate_operand(lhs);
|
||||
let rhs_val = self.translate_operand(rhs);
|
||||
|
||||
let is_signed = matches!(lhs.ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64);
|
||||
let ty = self.get_operand_type(lhs);
|
||||
let is_signed = matches!(ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64);
|
||||
|
||||
match op {
|
||||
BinaryOp::Add => self.builder.ins().iadd(lhs_val, rhs_val),
|
||||
|
||||
Reference in New Issue
Block a user