This commit is contained in:
nora 2023-05-27 22:54:11 +02:00
parent ebe01c7521
commit 92243712e7
4 changed files with 89 additions and 26 deletions

View file

@ -32,7 +32,7 @@
//! ``` //! ```
mod custom; mod custom;
mod info; pub mod info;
mod pretty; mod pretty;
mod validate; mod validate;
mod visit; mod visit;

View file

@ -38,9 +38,7 @@ pub fn last_register_uses(func: &Func<'_>) -> Vec<Option<Location>> {
let mut check_op = |op: Operand, stmt| match op { let mut check_op = |op: Operand, stmt| match op {
Operand::Reg(reg) => { Operand::Reg(reg) => {
dbg!(("reg use", reg, stmt));
if uses[reg.as_usize()].is_none() { if uses[reg.as_usize()].is_none() {
dbg!("insert!");
uses[reg.as_usize()] = Some(Location { bb, stmt }) uses[reg.as_usize()] = Some(Location { bb, stmt })
} }
} }
@ -70,6 +68,29 @@ pub fn last_register_uses(func: &Func<'_>) -> Vec<Option<Location>> {
uses uses
} }
pub fn dominates_location(f: &Func<'_>, dom: Location, sub: Location) -> bool {
// TODO: Can this be made more efficient by caching renumberings of bbs?
if dom.bb == sub.bb {
return match (dom.stmt, sub.stmt) {
(None, Some(_)) => true,
(Some(d_i), Some(s_i)) if d_i < s_i => true,
_ => false,
};
}
let mut seen = FxHashSet::default();
let mut worklist = vec![dom.bb];
while let Some(bb) = worklist.pop() {
if !seen.insert(bb) {
continue;
}
worklist.extend(f.bb(bb).term.successors());
}
seen.contains(&sub.bb)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{

View file

@ -54,7 +54,7 @@ pub fn generate<'cx>(lcx: &'cx LoweringCx<'cx>, ir: &Ir<'cx>) -> Result<()> {
let output = std::process::Command::new("cc") let output = std::process::Command::new("cc")
.arg("main.o") .arg("main.o")
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.stdout(Stdio::inherit()) .stderr(Stdio::inherit())
.output() .output()
.map_err(|err| analysis::Error::new_without_span(format!("failed to spawn `cc`: {err}")))?; .map_err(|err| analysis::Error::new_without_span(format!("failed to spawn `cc`: {err}")))?;

View file

@ -1,53 +1,53 @@
//! Basic codegen for the x86-64 architecture. //! Basic codegen for the x86-64 architecture.
//! //!
//! We use the [`iced_x86`] crate as our assembler. //! We use the [`iced_x86`] crate as our assembler.
//! //!
//! Then, all IR basic blocks and statements are lowered in a straightforward way. //! Then, all IR basic blocks and statements are lowered in a straightforward way.
//! No optimizations are done. There is some basic register allocation. //! No optimizations are done. There is some basic register allocation.
//! //!
//! # Register allocation //! # Register allocation
//! //!
//! Register allocation is not very smart, but also not too stupid. It tries to put SSA //! Register allocation is not very smart, but also not too stupid. It tries to put SSA
//! registers into machine registers as much as possible. //! registers into machine registers as much as possible.
//! //!
//! ```text //! ```text
//! bb0: //! bb0:
//! %0 = 0 //! %0 = 0
//! %1 = 1 //! %1 = 1
//! %2 = add %0 %1 //! %2 = add %0 %1
//! switch %2, then bb1, else bb2 //! switch %2, then bb1, else bb2
//! //!
//! bb1: //! bb1:
//! %3 = add %1, 1 //! %3 = add %1, 1
//! //!
//! bb2: //! bb2:
//! %4 = add %2, 2 //! %4 = add %2, 2
//! ``` //! ```
//! //!
//! For all SSA registers, we establish their "point of last use". This is the bb,stmt where their last usage occurs. //! For all SSA registers, we establish their "point of last use". This is the bb,stmt where their last usage occurs.
//! //!
//! First, we establish a list of possible registers to allocate. //! First, we establish a list of possible registers to allocate.
//! Since we immediately alloca all parameters, all the param registers are free real estate. //! Since we immediately alloca all parameters, all the param registers are free real estate.
//! Also, `rbx` is always saved on the stack at the start and end. //! Also, `rbx` is always saved on the stack at the start and end.
//! //!
//! ```text //! ```text
//! rax, rbx, rdi, rsi, rcx, rdx, r8, r9 //! rax, rbx, rdi, rsi, rcx, rdx, r8, r9
//! ``` //! ```
//! //!
//! This forms our priority list of registers. //! This forms our priority list of registers.
//! //!
//! Every time a statement has a return value, we try to assign that SSA register into a new machine register. //! Every time a statement has a return value, we try to assign that SSA register into a new machine register.
//! For this, we iterate through the register list above and find the first register that's free. If we see a register //! For this, we iterate through the register list above and find the first register that's free. If we see a register
//! that is not used anymore at the current location, we throw it out and use that new slot. //! that is not used anymore at the current location, we throw it out and use that new slot.
//! //!
//! When codegening an SSA register, we look into a lookup table from SSA register to machine register/stack spill and use that. //! When codegening an SSA register, we look into a lookup table from SSA register to machine register/stack spill and use that.
//! //!
//! When the list above is full, we spill the register to the stack. This should be rare. If the register doesn't fit into a machine //! When the list above is full, we spill the register to the stack. This should be rare. If the register doesn't fit into a machine
//! register, it's also spilled. //! register, it's also spilled.
//! //!
//! ## Registers //! ## Registers
//! <https://gitlab.com/x86-psABIs/x86-64-ABI> //! <https://gitlab.com/x86-psABIs/x86-64-ABI>
//! //!
//! | name | description | callee-saved | //! | name | description | callee-saved |
//! | -------- | -------------------- | ------------ | //! | -------- | -------------------- | ------------ |
//! | %rax | temporary register; with variable arguments passes information about the number of vector registers used; 1st return register | No | //! | %rax | temporary register; with variable arguments passes information about the number of vector registers used; 1st return register | No |
@ -66,12 +66,12 @@
//! | %r15 | callee-saved register; optionally used as GOT base pointer | Yes | //! | %r15 | callee-saved register; optionally used as GOT base pointer | Yes |
use analysis::{ use analysis::{
ir::{BbIdx, Func, Operand, Register, Statement, StatementKind}, ir::{self, BbIdx, Func, Location, Operand, Register, Statement, StatementKind},
LoweringCx, LoweringCx,
}; };
use iced_x86::{ use iced_x86::{
code_asm::{self as x, CodeAssembler}, code_asm::{self as x, CodeAssembler},
IcedError, IcedError,
}; };
use parser::Span; use parser::Span;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -91,22 +91,59 @@ impl<T> IcedErrExt for Result<T, IcedError> {
} }
} }
/// A machine register from our register list described in the module documentation.
#[derive(Debug, Clone, Copy)]
struct MachineReg(usize);
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum RegValue { enum RegValue {
/// The SSA register resides on the stack as it has been spilled.
Stack { offset: u64 }, Stack { offset: u64 },
/// The SSA register resides in a machine register
MachineReg(MachineReg),
} }
struct AsmCtxt<'cx> { struct AsmCtxt<'cx> {
lcx: &'cx LoweringCx<'cx>, lcx: &'cx LoweringCx<'cx>,
a: CodeAssembler, a: CodeAssembler,
reg_map: FxHashMap<Register, RegValue>, reg_map: FxHashMap<Register, RegValue>,
reg_occupancy: Vec<Option<Register>>,
current_stack_offset: u64, current_stack_offset: u64,
bb_idx: BbIdx, bb_idx: BbIdx,
// caches
last_register_uses: Vec<Option<Location>>,
} }
impl<'cx> AsmCtxt<'cx> { impl<'cx> AsmCtxt<'cx> {
fn allocate_result_ssa_reg(
&mut self,
f: &Func<'_>,
reg: Register,
location: Location,
) -> RegValue {
for (i, opt_reg) in self.reg_occupancy.iter_mut().enumerate() {
if let Some(reg) = opt_reg.as_mut() {
if let Some(last_use) = self.last_register_uses[reg.as_usize()] {
if ir::info::dominates_location(f, last_use, location) {
// The last use dominates our location - the SSA reg is dead now.
*opt_reg = None;
}
}
}
if opt_reg.is_none() {
*opt_reg = Some(reg);
return RegValue::MachineReg(MachineReg(i));
}
}
todo!("spill.")
}
fn generate_func(&mut self, func: &Func<'cx>) -> Result<()> { fn generate_func(&mut self, func: &Func<'cx>) -> Result<()> {
// TODO: Prologue // TODO: Prologue
self.a.push(x::rbx);
loop { loop {
let bb = &func.bbs[self.bb_idx.as_usize()]; let bb = &func.bbs[self.bb_idx.as_usize()];
@ -149,6 +186,7 @@ impl<'cx> AsmCtxt<'cx> {
let value = self.reg_map[&reg]; let value = self.reg_map[&reg];
let stack_offset = match value { let stack_offset = match value {
RegValue::Stack { offset } => offset, RegValue::Stack { offset } => offset,
RegValue::MachineReg(_) => todo!("machine reg"),
}; };
//let rhs = match value { //let rhs = match value {
// Operand::Const(c) => {} // Operand::Const(c) => {}
@ -167,24 +205,26 @@ impl<'cx> AsmCtxt<'cx> {
ptr, ptr,
size, size,
align, align,
} => todo!(), } => todo!("loads."),
StatementKind::BinOp { StatementKind::BinOp {
kind, kind,
lhs, lhs,
rhs, rhs,
result, result,
} => todo!(), } => todo!("binary operations"),
StatementKind::UnaryOperation { rhs, kind, result } => todo!(), StatementKind::UnaryOperation { rhs, kind, result } => {
todo!("unary operations")
}
StatementKind::PtrOffset { StatementKind::PtrOffset {
result, result,
ptr: reg, ptr: reg,
amount, amount,
} => todo!(), } => todo!("pointer offset :D"),
StatementKind::Call { StatementKind::Call {
result, result,
func, func,
ref args, ref args,
} => todo!(), } => todo!("function calls 💀"),
} }
} }
@ -205,8 +245,10 @@ pub fn generate_func<'cx>(lcx: &'cx LoweringCx<'cx>, func: &Func<'cx>) -> Result
lcx, lcx,
a, a,
reg_map: FxHashMap::default(), reg_map: FxHashMap::default(),
reg_occupancy: vec![None; 8],
current_stack_offset: 0, current_stack_offset: 0,
bb_idx: BbIdx(0), bb_idx: BbIdx(0),
last_register_uses: ir::info::last_register_uses(func),
}; };
cx.generate_func(func)?; cx.generate_func(func)?;