do some registers

This commit is contained in:
nora 2023-07-08 21:07:42 +02:00
parent a363b7c6d1
commit e28469fcc0
9 changed files with 227 additions and 97 deletions

View file

@ -1,3 +1,4 @@
mod registers;
mod x86_64;
use std::process::Stdio;
@ -18,6 +19,13 @@ pub fn generate<'cx>(lcx: &'cx LoweringCx<'cx>, ir: &Ir<'cx>) -> Result<()> {
object::Endianness::Little,
);
// GNU linkers have this awesome thing where they'll mark your stack as executable unless you tell them not to.
obj.add_section(
Vec::new(),
b".note.GNU-stack".to_vec(),
object::SectionKind::Note,
);
let text = obj.add_section(Vec::new(), b".text".to_vec(), object::SectionKind::Text);
for func in ir.funcs.values() {

122
codegen/src/registers.rs Normal file
View file

@ -0,0 +1,122 @@
//! # Register allocation
//!
//! Register allocation is not very smart, but also not too stupid. It tries to put SSA
//! registers into machine registers as much as possible.
//!
//! ```text
//! bb0:
//! %0 = 0
//! %1 = 1
//! %2 = add %0 %1
//! switch %2, then bb1, else bb2
//!
//! bb1:
//! %3 = add %1, 1
//!
//! bb2:
//! %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.
//!
//! First, we establish a list of possible registers to allocate.
//! Since we immediately alloca all parameters, all the param registers are free real estate.
//!
//! ```text
//! rax, rdi, rsi, rcx, rdx, r8, r9
//! ```
//!
//! 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.
//! 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.
//!
//! 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
//! register, it's also spilled.
//!
//! We do a first pass over the function to calculate all the offsets and registers
//! we want to use.
use std::cell::Cell;
use analysis::ir::{self, Func, Location, Register};
use rustc_hash::FxHashMap;
/// A machine register from our register list described in the module documentation.
#[derive(Debug, Clone, Copy)]
pub struct MachineReg(pub usize);
#[derive(Debug, Clone, Copy)]
pub enum RegValue {
/// The SSA register contains an address on the stack.
/// The offset is the offset from the start of the function.
StackRelative { offset: u64 },
/// The SSA register resides on the stack as it has been spilled.
/// This should be rather rare in practice.
Spilled { offset: u64 },
/// The SSA register resides in a machine register.
MachineReg(MachineReg),
}
#[derive(Debug)]
pub struct FunctionLayout {
/// Where a register comes from at a particular usage of a register.
register_uses: FxHashMap<(Location, Register), RegValue>,
total_stack_space: u64,
}
pub fn compute_layout(f: &Func) -> FunctionLayout {
let register_uses = FxHashMap::default();
FunctionLayout {
register_uses,
total_stack_space: 0,
}
}
pub struct LayoutPrinter<'a>(Cell<Option<&'a Func<'a>>>, &'a FunctionLayout);
impl<'a> ir::pretty::Customizer<'a> for LayoutPrinter<'a> {
fn start_func(&self, func: &'a Func<'a>) {
self.0.set(Some(func));
}
fn fmt_reg(
&self,
reg: Register,
f: &mut std::fmt::Formatter<'_>,
loc: Location,
) -> std::fmt::Result {
let layout = self.1.register_uses.get(&(loc, reg));
write!(f, "{{")?;
match self.0.get().unwrap().regs[reg.0 as usize].name {
None => write!(f, "%{}", reg.0)?,
Some(name) => write!(f, "%{name}")?,
}
write!(f, ", ")?;
match layout {
Some(RegValue::MachineReg(mach)) => write!(f, "reg-{}", mach.0)?,
Some(RegValue::Spilled { offset }) => write!(f, "spill-{offset}")?,
Some(RegValue::StackRelative { offset }) => {
write!(f, "i-forgot-what-this-meant-{offset}")?
}
None => write!(f, "<unknown>")?,
}
write!(f, "}}")
}
}
pub fn debug_layout(func: &Func, layout: &FunctionLayout) {
let custom = LayoutPrinter(Cell::default(), layout);
println!("----- code layout");
println!("{}", ir::pretty::func_to_string(func, &custom));
dbg!(layout);
}

View file

@ -5,46 +5,6 @@
//! Then, all IR basic blocks and statements are lowered in a straightforward way.
//! No optimizations are done. There is some basic register allocation.
//!
//! # Register allocation
//!
//! Register allocation is not very smart, but also not too stupid. It tries to put SSA
//! registers into machine registers as much as possible.
//!
//! ```text
//! bb0:
//! %0 = 0
//! %1 = 1
//! %2 = add %0 %1
//! switch %2, then bb1, else bb2
//!
//! bb1:
//! %3 = add %1, 1
//!
//! bb2:
//! %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.
//!
//! First, we establish a list of possible registers to allocate.
//! 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.
//!
//! ```text
//! rax, rbx, rdi, rsi, rcx, rdx, r8, r9
//! ```
//!
//! 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.
//! 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.
//!
//! 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
//! register, it's also spilled.
//!
//! ## Registers
//! <https://gitlab.com/x86-psABIs/x86-64-ABI>
//!
@ -78,7 +38,10 @@ use iced_x86::{
use parser::{Error, Span};
use rustc_hash::FxHashMap;
use crate::Result;
use crate::{
registers::{MachineReg, RegValue},
Result,
};
trait IcedErrExt {
type T;
@ -93,22 +56,6 @@ 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)]
enum RegValue {
/// The SSA register contains an address on the stack.
/// The offest is the offset from the start of the function.
StackRelative { offset: u64 },
/// The SSA register resides on the stack as it has been spilled.
/// This should be rather rare in practice.
Spilled { offset: u64 },
/// The SSA register resides in a machine register.
MachineReg(MachineReg),
}
struct AsmCtxt<'cx> {
lcx: &'cx LoweringCx<'cx>,
a: CodeAssembler,
@ -276,6 +223,9 @@ impl<'cx> AsmCtxt<'cx> {
pub fn generate_func<'cx>(lcx: &'cx LoweringCx<'cx>, func: &Func<'cx>) -> Result<Vec<u8>> {
assert_eq!(func.arity, 0, "arguments??? in MY uwucc????");
let layout = crate::registers::compute_layout(func);
crate::registers::debug_layout(func, &layout);
let fn_sp = func.def_span;
let a = CodeAssembler::new(64).sp(fn_sp)?;