use {
crate::{
ir::{FreshRegister, HardwareRegister, Instruction, TypedHardwareRegister, Variable},
liveness::{Lifetime, Lifetimes},
reification::{Index, RegisterType, ReifiedRegister},
FreshVariable,
},
std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
};
pub type AllocatedVariable = Variable<TypedHardwareRegister>;
#[derive(Debug)]
struct RegisterAllocator {
free_registers: BTreeSet<HardwareRegister>,
pinned: PinnedOutputRegisters,
}
#[derive(Debug)]
struct PinnedOutputRegisters {
iter: BTreeSet<HardwareRegister>,
reservations: BTreeMap<HardwareRegister, (FreshRegister, usize)>,
}
impl PinnedOutputRegisters {
fn new(iter: impl Iterator<Item = u64>) -> Self {
Self {
iter: BTreeSet::from_iter(iter.map(HardwareRegister)),
reservations: BTreeMap::new(),
}
}
fn reserve_output_register(
&mut self,
lifetimes: &Lifetimes,
reified_register: &ReifiedRegister<FreshRegister>,
) -> bool {
match self.iter.pop_first() {
Some(hardware_register) => {
let lifetime = lifetimes[reified_register.reg].begin;
self.reservations
.insert(hardware_register, (reified_register.reg, lifetime));
true
}
None => false,
}
}
}
impl RegisterAllocator {
fn new<T>(registers: T) -> Self
where
T: Iterator<Item = u64> + Clone,
{
let pool = BTreeSet::from_iter(registers.clone().map(HardwareRegister));
RegisterAllocator {
free_registers: pool,
pinned: PinnedOutputRegisters::new(registers),
}
}
fn pop_first(&mut self, reg: FreshRegister, end_lifetime: usize) -> Option<HardwareRegister> {
let reg = self
.free_registers
.iter()
.find(
|&hardware_register| match self.pinned.reservations.get(hardware_register) {
Some((tp, _lifetime)) if reg == *tp => true,
Some((_tp, lifetime)) if end_lifetime <= *lifetime => true,
None => true,
_ => false,
},
)
.copied();
if let Some(hardware_register) = reg {
self.free_registers.remove(&hardware_register);
}
reg
}
fn insert(&mut self, register: HardwareRegister) -> bool {
self.free_registers.insert(register)
}
}
pub fn allocate_input_variable(
mapping: &mut RegisterMapping,
register_bank: &mut RegisterBank,
input_hw_registers: Vec<FreshVariable>,
lifetimes: &Lifetimes,
) -> Vec<AllocatedVariable> {
input_hw_registers
.into_iter()
.map(|variable| {
let registers = variable
.registers
.into_iter()
.map(|register| {
mapping
.get_or_allocate_register(register_bank, register, lifetimes[register.reg])
.to_basic_register()
})
.collect();
AllocatedVariable {
label: variable.label,
registers,
}
})
.collect()
}
pub fn reserve_output_variable(
register_bank: &mut RegisterBank,
lifetimes: &Lifetimes,
variable: &FreshVariable,
) {
let pool = register_bank.get_register_pool(variable.registers[0].r#type);
for reified_register in &variable.registers {
if !pool
.pinned
.reserve_output_register(lifetimes, reified_register)
{
panic!(
"Ran out of registers to reserve {}! Reduce the number of outputs.",
variable.label
)
}
}
}
#[derive(Debug)]
pub struct RegisterBank {
general_purpose: RegisterAllocator,
vector: RegisterAllocator,
}
impl Default for RegisterBank {
fn default() -> Self {
Self::new()
}
}
impl RegisterBank {
pub fn new() -> Self {
Self {
general_purpose: RegisterAllocator::new((0..=17).chain(20..29)),
vector: RegisterAllocator::new(0..=31),
}
}
fn get_register_pool(&mut self, r#type: RegisterType) -> &mut RegisterAllocator {
match r#type {
RegisterType::X => &mut self.general_purpose,
RegisterType::V | RegisterType::D => &mut self.vector,
}
}
fn pop_first(
&mut self,
reified_register: ReifiedRegister<FreshRegister>,
end_lifetime: usize,
) -> Option<ReifiedRegister<HardwareRegister>> {
let hw_reg = self
.get_register_pool(reified_register.r#type)
.pop_first(reified_register.reg, end_lifetime);
hw_reg.map(|reg| reified_register.into_hardware(reg))
}
fn insert(&mut self, register: TypedHardwareRegister) -> bool {
match register {
TypedHardwareRegister::General(hardware_register) => {
self.general_purpose.insert(hardware_register)
}
TypedHardwareRegister::Vector(hardware_register) => {
self.vector.insert(hardware_register)
}
}
}
}
#[derive(Debug, Default)]
pub struct RegisterMapping {
mapping: HashMap<FreshRegister, TypedHardwareRegister>,
}
impl RegisterMapping {
pub fn new() -> Self {
Self {
mapping: HashMap::with_capacity(100),
}
}
pub fn allocated(&self) -> usize {
self.mapping.len()
}
fn get_register(
&self,
fresh: ReifiedRegister<FreshRegister>,
) -> ReifiedRegister<HardwareRegister> {
match self.mapping.get(&fresh.reg) {
Some(reg) => fresh.into_hardware(reg.reg()),
None => panic!(
"Internal error: {fresh:?} has not been assigned yet. This should not be possible."
),
}
}
pub fn get_or_allocate_register(
&mut self,
register_bank: &mut RegisterBank,
typed_register: ReifiedRegister<FreshRegister>,
lifetime: Lifetime,
) -> ReifiedRegister<HardwareRegister> {
match self.mapping.get(&typed_register.reg) {
Some(reg) => typed_register.into_hardware(reg.reg()),
None => {
let hardware_reified_register = register_bank
.pop_first(typed_register, lifetime.end)
.unwrap_or_else(|| {
panic!(
"All register are in use. HLA does not support spilling to stack for \
performance reasons. Reduce the number of registers simultaneously \
in use."
)
});
self.mapping.insert(
typed_register.reg,
hardware_reified_register.to_basic_register(),
);
hardware_reified_register
}
}
}
fn free_register(&mut self, register_bank: &mut RegisterBank, fresh: FreshRegister) -> bool {
if let Some(reg) = self.mapping.remove(&fresh) {
let result = register_bank.insert(reg);
assert!(
result,
"hardware:{reg:?} is assigned to more than one fresh register."
);
result
} else {
panic!(
"Trying to free a fresh register that has not been assigned a hardware register: \
{fresh}"
)
}
}
pub fn get_allocated_variable(&mut self, variable: &FreshVariable) -> AllocatedVariable {
AllocatedVariable {
label: variable.label.clone(),
registers: variable
.registers
.iter()
.map(|register| {
{
self.mapping
.get(®ister.reg)
.map(|hw_reg| {
ReifiedRegister {
reg: hw_reg.reg(),
r#type: register.r#type,
idx: Index::None,
}
.to_basic_register()
})
.unwrap_or_else(|| {
panic!("Internal error: {} has not been allocated.", variable.label)
})
}
})
.collect(),
}
}
}
pub fn hardware_register_allocation(
mapping: &mut RegisterMapping,
register_bank: &mut RegisterBank,
instructions: Vec<Instruction<FreshRegister>>,
releases: VecDeque<HashSet<FreshRegister>>,
lifetimes: Lifetimes,
) -> Vec<Instruction<HardwareRegister>> {
assert_eq!(
instructions.len(),
releases.len(),
"The instructions and release collections need to be the same length"
);
instructions
.into_iter()
.zip(releases)
.map(|(instruction, release)| {
let src = instruction
.operands
.into_iter()
.map(|s| mapping.get_register(s))
.collect();
release.into_iter().for_each(|fresh| {
mapping.free_register(register_bank, fresh);
});
let dest = instruction
.results
.into_iter()
.map(|d| {
let idx = d.reg;
mapping.get_or_allocate_register(register_bank, d, lifetimes[idx])
})
.collect();
Instruction {
opcode: instruction.opcode,
results: dest,
operands: src,
modifiers: instruction.modifiers,
}
})
.collect()
}