Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/asm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! This module provides the assembly code generation backend,
//! translating the compiler's IR into target-specific assembly.
//! Assembly code generation backend: translates the compiler's IR
//! into target-specific assembly.

pub mod aarch64;
pub mod common;
Expand Down
60 changes: 17 additions & 43 deletions src/asm/aarch64.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! AArch64 backend driver: lowers an IR module to instructions over
//! virtual registers, register-allocates each function, and delegates
//! textual emission of the final assembly to [`printer::AsmPrinter`].

mod aapcs;
mod frame;
mod function_generator;
Expand All @@ -7,20 +11,18 @@ mod printer;
mod register_allocator;
mod types;

pub use inst::Instruction;
pub use types::{BinOp, Operand, Register};

use crate::asm::common::StructLayouts;
use crate::asm::error::Error;
use crate::common::{Generator, Target};
use crate::ir;
use aapcs::{classify_args, ArgumentLocation};
use frame::FrameLayout;
use function_generator::FunctionGenerator;
use printer::{AsmPrint, AsmPrinter};
use function_generator::AsmFunctionGenerator;
use inst::Instruction;
use printer::AsmPrinter;
use register_allocator::RegisterAllocator;
use std::io::Write;
use types::RegisterSize;
use types::{InstOperand, Register, RegisterSize};

struct GeneratedGlobal {
symbol: String,
Expand Down Expand Up @@ -70,6 +72,9 @@ fn stream_uses_fp(insts: &[Instruction]) -> bool {
})
}

/// AArch64 assembly generator: runs instruction selection and register
/// allocation for every function of the module, together with data
/// layout for globals; the `Generator` impl drives both phases.
pub struct AArch64AsmGenerator<'a> {
module: &'a ir::Module,
registry: &'a ir::Registry,
Expand Down Expand Up @@ -121,42 +126,11 @@ impl<'a> Generator for AArch64AsmGenerator<'a> {
Ok(())
}

/// Delegates to [`AsmPrinter::emit_program`], mirroring how the ir
/// layer's `output` delegates to `IrPrinter::emit_module` — the
/// printer owns section layout, symbol emission, and prologues.
fn output<W: Write>(&self, w: &mut W) -> Result<(), Error> {
let mut printer = AsmPrinter::new(w, self.target);

if !self.globals.is_empty() {
printer.emit_section("data")?;
for g in &self.globals {
printer.emit_global(&g.symbol)?;
printer.emit_align(2)?;
printer.emit_label(&g.symbol)?;
match &g.data {
GlobalData::Word { value } => printer.emit_word(*value)?,
GlobalData::Array { words, zero_bytes } => {
for v in words {
printer.emit_word(*v)?;
}
if *zero_bytes > 0 {
printer.emit_zero(*zero_bytes)?;
}
}
}
}
printer.emit_newline()?;
}

printer.emit_section("text")?;
for func in &self.functions {
printer.emit_global(&func.symbol)?;
printer.emit_align(2)?;
printer.emit_label(&func.symbol)?;
printer.set_uses_fp(func.uses_fp);
printer.emit_prologue(func.frame_size)?;
printer.emit_insts(&func.insts)?;
printer.emit_newline()?;
}

Ok(())
AsmPrinter::new(w, self.target).emit_program(&self.globals, &self.functions)
}
}

Expand All @@ -179,7 +153,7 @@ impl<'a> AArch64AsmGenerator<'a> {
ArgumentLocation::Gpr(n) => Instruction::Mov {
size,
dst,
src: Operand::Register(Register::Physical(n)),
src: InstOperand::Register(Register::Physical(n)),
},
ArgumentLocation::Fpr(_) => todo!(
"asmt-4: lift an incoming f32 argument out of its `s_` register \
Expand Down Expand Up @@ -253,7 +227,7 @@ impl<'a> AArch64AsmGenerator<'a> {
let mut frame = FrameLayout::from_blocks(&body.blocks, layouts)?;
let mut insts = Self::handle_arguments(body)?;
insts.extend(
FunctionGenerator::new(&symbol, &frame, layouts, target, body.next_vreg)
AsmFunctionGenerator::new(&symbol, &frame, layouts, target, body.next_vreg)
.generate(&body.blocks)?,
);

Expand Down
11 changes: 5 additions & 6 deletions src/asm/aarch64/aapcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub const OUTGOING_ARG_SLOT_BYTES: i64 = 8;
/// AAPCS64 location of a single argument at the call boundary.
///
/// `Gpr(n)` / `Fpr(n)` carry the architectural register index; pair
/// the index with the operand's [`super::types::RegSize`] to obtain
/// the concrete `w_`/`x_`/`s_` form.
/// the index with the operand's [`super::types::RegisterSize`] to
/// obtain the concrete `w_`/`x_`/`s_` form.
///
/// `Stack { offset }` is the byte offset within the outgoing-arg
/// area. The two sides interpret the offset against different bases:
Expand Down Expand Up @@ -91,10 +91,9 @@ where
/// advances. Kept internal to this module; callers consume the
/// resolved [`ArgumentLocation`] instead.
///
/// `Float` is currently unreachable because `Dtype::F32` is not yet a
/// variant of [`ir::Dtype`]; the arm is part of the classifier's
/// AAPCS64 specification and becomes live as soon as the
/// `TryFrom<&ir::Dtype>` impl learns to map `Dtype::F32` to it.
/// `Float` is unreachable while [`ir::Dtype`] lacks an `F32` variant;
/// the arm is kept as part of the classifier's AAPCS64 specification
/// so adding the variant only extends the `TryFrom<&ir::Dtype>` impl.
enum ArgumentClass {
Int,
#[allow(dead_code)]
Expand Down
87 changes: 45 additions & 42 deletions src/asm/aarch64/function_generator.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
//! Instruction selection: lowers IR statements to AArch64 instructions
//! over virtual registers, including ABI handling for arguments, calls,
//! and returns.

use super::aapcs::{classify_args, ArgumentLocation};
use super::frame::{outgoing_arg_addr, outgoing_stack_bytes, FrameLayout};
use super::inst::Instruction;
use super::phi_lowering::{self, ParallelCopy, SplitEdge};
use super::types::{
Addr, BinOp, Cond, IndexOperand, Operand, Register, RegisterSize, REG_IP0, REG_X0,
Addr, Cond, InstBinOp, InstOperand, Register, RegisterSize, REG_IP0, REG_X0,
};
use crate::asm::common::{StackSlot, StructLayouts};
use crate::asm::error::Error;
Expand All @@ -29,7 +33,7 @@ pub enum PtrBase {
Register(usize),
}

pub struct FunctionGenerator<'a> {
pub struct AsmFunctionGenerator<'a> {
func_id: &'a str,
frame: &'a FrameLayout,
layouts: &'a StructLayouts,
Expand All @@ -39,7 +43,7 @@ pub struct FunctionGenerator<'a> {
cond_map: HashMap<usize, Cond>,
}

impl<'a> FunctionGenerator<'a> {
impl<'a> AsmFunctionGenerator<'a> {
/// Creates a generator for one function body. `next_vreg` seeds the
/// virtual-register counter from the IR body's high-water mark so the
/// copies introduced during phi lowering get fresh ids.
Expand Down Expand Up @@ -167,15 +171,15 @@ impl<'a> FunctionGenerator<'a> {
let addr = self.lower_ptr_as_addr(&s.ptr)?;

match src {
Operand::Register(r) => {
InstOperand::Register(r) => {
self.insts.push(Instruction::Str { size, src: r, addr });
}
Operand::Immediate(imm) => {
InstOperand::Immediate(imm) => {
let tmp = self.fresh_vreg();
self.insts.push(Instruction::Mov {
size,
dst: Register::Virtual(tmp),
src: Operand::Immediate(imm),
src: InstOperand::Immediate(imm),
});
self.insts.push(Instruction::Str {
size,
Expand Down Expand Up @@ -451,11 +455,11 @@ impl<'a> FunctionGenerator<'a> {
});
if offset != 0 {
self.insts.push(Instruction::BinOp {
op: BinOp::Add,
op: InstBinOp::Add,
size: RegisterSize::X64,
dst: Register::Virtual(dst),
lhs: Register::Virtual(dst),
rhs: Operand::Immediate(offset),
rhs: InstOperand::Immediate(offset),
});
}
}
Expand Down Expand Up @@ -528,13 +532,13 @@ impl<'a> FunctionGenerator<'a> {

let (op, size) = self.lower_value(arg)?;
let src_reg = match op {
Operand::Register(r) => r,
Operand::Immediate(imm) => {
InstOperand::Register(r) => r,
InstOperand::Immediate(imm) => {
let scratch = Register::Physical(REG_IP0);
self.insts.push(Instruction::Mov {
size,
dst: scratch,
src: Operand::Immediate(imm),
src: InstOperand::Immediate(imm),
});
scratch
}
Expand All @@ -549,9 +553,10 @@ impl<'a> FunctionGenerator<'a> {

/// Lifts the AAPCS64 return value out of `x0` (or `s0` for FP
/// returns) into the vreg named by `res`. Dispatch is driven by
/// the return dtype's [`RegSize`], so adding a new scalar class
/// (e.g. `Dtype::F32 -> RegSize::S32`) requires only that
/// `RegisterSize`'s `TryFrom<&ir::Dtype>` learns the new mapping.
/// the return dtype's [`RegisterSize`], so adding a new scalar
/// class (e.g. `Dtype::F32 -> RegisterSize::S32`) requires only
/// that `RegisterSize`'s `TryFrom<&ir::Dtype>` learns the new
/// mapping.
fn emit_call_result(&mut self, res: &ir::Local) -> Result<(), Error> {
let dst = Register::Virtual(res.id.0);
let size = RegisterSize::try_from(&res.dtype)?;
Expand All @@ -566,7 +571,7 @@ impl<'a> FunctionGenerator<'a> {
self.insts.push(Instruction::Mov {
size: RegisterSize::X64,
dst,
src: Operand::Register(Register::Virtual(v)),
src: InstOperand::Register(Register::Virtual(v)),
});
}
PtrBase::Stack => {
Expand All @@ -586,9 +591,9 @@ impl<'a> FunctionGenerator<'a> {
Ok(())
}

fn lower_int(&self, val: &ir::Operand) -> Result<Operand, Error> {
fn lower_int(&self, val: &ir::Operand) -> Result<InstOperand, Error> {
match val {
ir::Operand::Const(c) => Ok(Operand::Immediate(c.val)),
ir::Operand::Const(c) => Ok(InstOperand::Immediate(c.val)),
ir::Operand::Local(l) => {
if !matches!(l.dtype, ir::Dtype::I1 | ir::Dtype::I32) {
return Err(Error::UnsupportedDtype {
Expand All @@ -600,7 +605,7 @@ impl<'a> FunctionGenerator<'a> {
what: format!("int operand references alloca pointer %r{}", l.id.0),
});
}
Ok(Operand::Register(Register::Virtual(l.id.0)))
Ok(InstOperand::Register(Register::Virtual(l.id.0)))
}
ir::Operand::Global(_) => Err(Error::UnsupportedOperand {
what: format!("unsupported int operand: {}", val),
Expand All @@ -610,22 +615,22 @@ impl<'a> FunctionGenerator<'a> {

fn lower_int_to_reg(&mut self, val: &ir::Operand) -> Result<Register, Error> {
match self.lower_int(val)? {
Operand::Register(r) => Ok(r),
Operand::Immediate(imm) => {
InstOperand::Register(r) => Ok(r),
InstOperand::Immediate(imm) => {
let tmp = self.fresh_vreg();
self.insts.push(Instruction::Mov {
size: RegisterSize::W32,
dst: Register::Virtual(tmp),
src: Operand::Immediate(imm),
src: InstOperand::Immediate(imm),
});
Ok(Register::Virtual(tmp))
}
}
}

fn lower_value(&self, val: &ir::Operand) -> Result<(Operand, RegisterSize), Error> {
fn lower_value(&self, val: &ir::Operand) -> Result<(InstOperand, RegisterSize), Error> {
match val {
ir::Operand::Const(c) => Ok((Operand::Immediate(c.val), RegisterSize::W32)),
ir::Operand::Const(c) => Ok((InstOperand::Immediate(c.val), RegisterSize::W32)),
ir::Operand::Local(l) => {
let size = match &l.dtype {
ir::Dtype::I1 | ir::Dtype::I32 => RegisterSize::W32,
Expand All @@ -646,7 +651,7 @@ impl<'a> FunctionGenerator<'a> {
})
}
};
Ok((Operand::Register(Register::Virtual(l.id.0)), size))
Ok((InstOperand::Register(Register::Virtual(l.id.0)), size))
}
ir::Operand::Global(_) => Err(Error::UnsupportedOperand {
what: "unexpected global variable in value position".into(),
Expand All @@ -673,18 +678,16 @@ impl<'a> FunctionGenerator<'a> {
match val {
ir::Operand::Local(l) => {
let vreg_index = l.id.0;
// Check if this local is a stack allocation (alloca).
// Allocas have their address implicitly defined by their stack slot,
// rather than being stored in a register.
// An alloca's address is its stack slot, not a value held
// in a register.
if let Some(slot) = self.frame.alloca_slot(vreg_index) {
return Ok((PtrBase::Stack, Some(slot)));
}
// Otherwise, if it's a pointer type, the pointer value itself
// lives in a virtual register (e.g., result of a GEP or load).
// A non-alloca pointer local holds its address in a virtual
// register (e.g., the result of a GEP or load).
if matches!(l.dtype, ir::Dtype::Pointer { .. }) {
return Ok((PtrBase::Register(vreg_index), None));
}
// Non-pointer locals cannot be used as pointer operands.
Err(Error::UnsupportedDtype {
dtype: l.dtype.clone(),
})
Expand All @@ -699,9 +702,9 @@ impl<'a> FunctionGenerator<'a> {
}
}

fn lower_index(&self, val: &ir::Operand) -> Result<IndexOperand, Error> {
fn lower_index(&self, val: &ir::Operand) -> Result<InstOperand, Error> {
match val {
ir::Operand::Const(c) => Ok(IndexOperand::Imm(c.val)),
ir::Operand::Const(c) => Ok(InstOperand::Immediate(c.val)),
ir::Operand::Local(l) => {
if !matches!(l.dtype, ir::Dtype::I1 | ir::Dtype::I32) {
return Err(Error::UnsupportedDtype {
Expand All @@ -713,7 +716,7 @@ impl<'a> FunctionGenerator<'a> {
what: format!("index operand references alloca pointer %r{}", l.id.0),
});
}
Ok(IndexOperand::Reg(Register::Virtual(l.id.0)))
Ok(InstOperand::Register(Register::Virtual(l.id.0)))
}
ir::Operand::Global(_) => Err(Error::UnsupportedOperand {
what: format!("unsupported index operand: {}", val),
Expand Down Expand Up @@ -761,8 +764,8 @@ impl<'a> FunctionGenerator<'a> {
let size = RegisterSize::try_from(dst.dtype())?;

let src_op = match src {
ir::Operand::Const(c) => Operand::Immediate(c.val),
ir::Operand::Local(l) => Operand::Register(Register::Virtual(l.id.0)),
ir::Operand::Const(c) => InstOperand::Immediate(c.val),
ir::Operand::Local(l) => InstOperand::Register(Register::Virtual(l.id.0)),
ir::Operand::Global(_) => {
return Err(Error::UnsupportedOperand {
what: "global variable in phi copy".into(),
Expand Down Expand Up @@ -801,12 +804,12 @@ impl<'a> FunctionGenerator<'a> {
}
}

fn arith_op_to_binop(op: &ir::stmt::ArithBinOp) -> BinOp {
fn arith_op_to_binop(op: &ir::stmt::ArithBinOp) -> InstBinOp {
match op {
ir::stmt::ArithBinOp::Add => BinOp::Add,
ir::stmt::ArithBinOp::Sub => BinOp::Sub,
ir::stmt::ArithBinOp::Mul => BinOp::Mul,
ir::stmt::ArithBinOp::SDiv => BinOp::SDiv,
ir::stmt::ArithBinOp::Add => InstBinOp::Add,
ir::stmt::ArithBinOp::Sub => InstBinOp::Sub,
ir::stmt::ArithBinOp::Mul => InstBinOp::Mul,
ir::stmt::ArithBinOp::SDiv => InstBinOp::SDiv,
}
}

Expand All @@ -815,7 +818,7 @@ fn arith_op_to_binop(op: &ir::stmt::ArithBinOp) -> BinOp {
/// and `s0` for floating-point returns. Selection is driven by
/// `size` so that adding a new scalar class only requires
/// `RegisterSize`'s `TryFrom<&ir::Dtype>` to learn the new mapping.
fn return_inst(size: RegisterSize, src: Operand) -> Instruction {
fn return_inst(size: RegisterSize, src: InstOperand) -> Instruction {
match size {
RegisterSize::W32 | RegisterSize::X64 => Instruction::Mov {
size,
Expand All @@ -836,7 +839,7 @@ fn return_value_load(size: RegisterSize, dst: Register) -> Instruction {
RegisterSize::W32 | RegisterSize::X64 => Instruction::Mov {
size,
dst,
src: Operand::Register(Register::Physical(REG_X0)),
src: InstOperand::Register(Register::Physical(REG_X0)),
},
RegisterSize::S32 => todo!(
"asmt-4: lift an f32 call result out of the FP return register `s0` \
Expand Down
Loading