use crate::{
asm_generation::fuel::compiler_constants,
asm_lang::{
allocated_ops::AllocatedRegister, virtual_register::*, ControlFlowOp, Label, Op, VirtualOp,
},
};
use either::Either;
use petgraph::graph::{node_index, NodeIndex};
use rustc_hash::FxHashSet;
use std::collections::{BTreeSet, HashMap};
pub type InterferenceGraph =
petgraph::stable_graph::StableGraph<Option<VirtualRegister>, (), petgraph::Undirected>;
#[derive(Debug)]
struct RegisterAllocationStatus {
reg: AllocatedRegister,
used_by: BTreeSet<VirtualRegister>,
}
#[derive(Debug)]
pub(crate) struct RegisterPool {
registers: Vec<RegisterAllocationStatus>,
}
impl RegisterPool {
fn init() -> Self {
let reg_pool: Vec<RegisterAllocationStatus> = (0
..compiler_constants::NUM_ALLOCATABLE_REGISTERS)
.map(|x| RegisterAllocationStatus {
reg: AllocatedRegister::Allocated(x),
used_by: BTreeSet::new(),
})
.collect();
Self {
registers: reg_pool,
}
}
pub(crate) fn get_register(
&self,
virtual_register: &VirtualRegister,
) -> Option<AllocatedRegister> {
let allocated_reg =
self.registers
.iter()
.find(|RegisterAllocationStatus { reg: _, used_by }| {
used_by.contains(virtual_register)
});
allocated_reg.map(|RegisterAllocationStatus { reg, used_by: _ }| reg.clone())
}
}
pub(crate) fn liveness_analysis(ops: &[Op]) -> Vec<FxHashSet<VirtualRegister>> {
let mut live_in: Vec<FxHashSet<VirtualRegister>> = vec![FxHashSet::default(); ops.len()];
let mut live_out: Vec<FxHashSet<VirtualRegister>> = vec![FxHashSet::default(); ops.len()];
let mut label_to_index: HashMap<Label, usize> = HashMap::new();
for (idx, op) in ops.iter().enumerate() {
if let Either::Right(ControlFlowOp::Label(op_label)) = op.opcode {
label_to_index.insert(op_label, idx);
}
}
let mut modified = true;
while modified {
modified = false;
for (ix, op) in ops.iter().rev().enumerate() {
let mut local_modified = false;
let rev_ix = ops.len() - ix - 1;
let mut op_use = op.use_registers();
let mut op_def = op.def_registers();
op_use.retain(|®| matches!(reg, VirtualRegister::Virtual(_)));
op_def.retain(|®| matches!(reg, VirtualRegister::Virtual(_)));
for s in &op.successors(rev_ix, ops, &label_to_index) {
for l in live_in[*s].iter() {
local_modified |= live_out[rev_ix].insert(l.clone());
}
}
for u in op_use {
local_modified |= live_in[rev_ix].insert(u.clone());
}
for l in live_out[rev_ix].iter() {
if !op_def.contains(&l) {
local_modified |= live_in[rev_ix].insert(l.clone());
}
}
modified |= local_modified;
}
}
live_out
}
pub(crate) fn create_interference_graph(
ops: &[Op],
live_out: &[FxHashSet<VirtualRegister>],
) -> (InterferenceGraph, HashMap<VirtualRegister, NodeIndex>) {
let mut interference_graph = InterferenceGraph::with_capacity(0, 0);
let mut reg_to_node_map: HashMap<VirtualRegister, NodeIndex> = HashMap::new();
ops.iter()
.fold(BTreeSet::new(), |mut tree, elem| {
let mut regs = elem.registers();
regs.retain(|®| matches!(reg, VirtualRegister::Virtual(_)));
tree.extend(regs.into_iter());
tree
})
.iter()
.for_each(|®| {
reg_to_node_map.insert(reg.clone(), interference_graph.add_node(Some(reg.clone())));
});
for (ix, regs) in live_out.iter().enumerate() {
match &ops[ix].opcode {
Either::Left(VirtualOp::MOVE(v, c)) => {
if let Some(ix1) = reg_to_node_map.get(v) {
for b in regs.iter() {
if let Some(ix2) = reg_to_node_map.get(b) {
if *b != *c && *b != *v && !interference_graph.contains_edge(*ix1, *ix2)
{
interference_graph.add_edge(*ix1, *ix2, ());
}
}
}
}
}
_ => {
for v in &ops[ix].def_registers() {
if let Some(ix1) = reg_to_node_map.get(v) {
for b in regs.iter() {
if let Some(ix2) = reg_to_node_map.get(b) {
if *b != **v && !interference_graph.contains_edge(*ix1, *ix2) {
interference_graph.add_edge(*ix1, *ix2, ());
}
}
}
}
}
}
}
}
(interference_graph, reg_to_node_map)
}
pub(crate) fn coalesce_registers(
ops: &[Op],
interference_graph: &mut InterferenceGraph,
reg_to_node_map: &mut HashMap<VirtualRegister, NodeIndex>,
) -> Vec<Op> {
let mut reg_to_reg_map: HashMap<&VirtualRegister, &VirtualRegister> = HashMap::new();
let mut reduced_ops: Vec<Op> = Vec::with_capacity(ops.len());
for op in ops {
match &op.opcode {
Either::Left(VirtualOp::MOVE(x, y)) => {
match (x, y) {
(VirtualRegister::Virtual(_), VirtualRegister::Virtual(_)) => {
let mut r1 = x;
while let Some(t) = reg_to_reg_map.get(r1) {
r1 = t;
}
let mut r2 = y;
while let Some(t) = reg_to_reg_map.get(r2) {
r2 = t;
}
let ix1 = reg_to_node_map.get(r1).unwrap();
let ix2 = reg_to_node_map.get(r2).unwrap();
if r1 == r2 {
continue;
}
if interference_graph.contains_edge(*ix1, *ix2) {
reduced_ops.push(op.clone());
continue;
}
for neighbor in interference_graph.neighbors(*ix2).collect::<Vec<_>>() {
interference_graph.add_edge(neighbor, *ix1, ());
}
interference_graph[*ix2] = None;
reg_to_node_map.insert(r2.clone(), *ix1);
reg_to_reg_map.insert(r2, r1);
}
_ => {
reduced_ops.push(op.clone());
}
}
}
_ => {
reduced_ops.push(op.clone());
}
}
}
let mut final_reg_to_reg_map: HashMap<&VirtualRegister, &VirtualRegister> = HashMap::new();
for reg in reg_to_reg_map.keys() {
let mut temp = reg;
while let Some(t) = reg_to_reg_map.get(temp) {
temp = t;
}
final_reg_to_reg_map.insert(reg, temp);
}
for new_op in &mut reduced_ops {
*new_op = new_op.update_register(&final_reg_to_reg_map);
}
reduced_ops
}
pub(crate) fn color_interference_graph(
interference_graph: &mut InterferenceGraph,
) -> Vec<(VirtualRegister, BTreeSet<VirtualRegister>)> {
let mut stack = Vec::with_capacity(interference_graph.node_count());
for index in 0..interference_graph.node_count() {
let node = node_index(index);
if interference_graph[node].is_none() {
continue;
}
let neighbors = interference_graph
.neighbors(node)
.filter_map(|n| interference_graph[n].clone())
.collect();
stack.push((interference_graph[node].clone().unwrap(), neighbors));
interference_graph[node] = None;
}
stack
}
pub(crate) fn assign_registers(
stack: &mut Vec<(VirtualRegister, BTreeSet<VirtualRegister>)>,
) -> RegisterPool {
let mut pool = RegisterPool::init();
while let Some((reg, neighbors)) = stack.pop() {
if matches!(reg, VirtualRegister::Virtual(_)) {
let available =
pool.registers
.iter_mut()
.find(|RegisterAllocationStatus { reg: _, used_by }| {
neighbors.intersection(used_by).count() == 0
});
if let Some(RegisterAllocationStatus { reg: _, used_by }) = available {
used_by.insert(reg.clone());
} else {
unimplemented!(
"The allocator cannot resolve a register mapping for this program. \
This is a temporary artifact of the extremely early stage version \
of this language. Try to lower the number of variables you use."
);
}
}
}
pool
}