use std::{
cmp::Reverse,
collections::{BTreeMap, BinaryHeap},
};
use rayon::prelude::*;
use crate::{
analysis::{
AnalysisResults, DataFlowSolver, LiveVariables, LivenessResult, SsaCfg, SsaFunction, SsaOp,
SsaType, SsaVarId, VariableOrigin,
},
utils::BitSet,
Error, Result,
};
pub struct InterferenceGraph {
edges: BTreeMap<SsaVarId, BitSet>,
var_types: BTreeMap<SsaVarId, SsaType>,
var_count: usize,
}
impl InterferenceGraph {
fn new(var_count: usize) -> Self {
Self {
edges: BTreeMap::new(),
var_types: BTreeMap::new(),
var_count,
}
}
fn add_edge(&mut self, a: SsaVarId, b: SsaVarId) {
if a != b {
let var_count = self.var_count;
self.edges
.entry(a)
.or_insert_with(|| BitSet::new(var_count))
.insert(b.index());
self.edges
.entry(b)
.or_insert_with(|| BitSet::new(var_count))
.insert(a.index());
}
}
fn set_type(&mut self, var: SsaVarId, ty: SsaType) {
self.var_types.insert(var, ty);
}
fn neighbors(&self, var: SsaVarId) -> impl Iterator<Item = SsaVarId> + '_ {
self.edges
.get(&var)
.into_iter()
.flat_map(|set| set.iter().map(SsaVarId::from_index))
}
fn degree(&self, var: SsaVarId) -> usize {
self.edges.get(&var).map_or(0, |s| s.count())
}
}
pub struct LocalAllocation {
pub var_to_local: BTreeMap<SsaVarId, u16>,
pub num_locals: u16,
pub original_to_compacted: BTreeMap<u16, u16>,
}
#[derive(Debug, Clone)]
struct LiveInterval {
start: usize,
end: usize,
}
impl LiveInterval {
fn new(pos: usize) -> Self {
Self {
start: pos,
end: pos + 1,
}
}
fn extend_start(&mut self, pos: usize) {
self.start = self.start.min(pos);
}
fn extend_end(&mut self, pos: usize) {
self.end = self.end.max(pos);
}
}
pub struct LocalCoalescer {
interference: InterferenceGraph,
coalescable_vars: Vec<SsaVarId>,
precomputed: Option<LocalAllocation>,
}
const LINEAR_SCAN_THRESHOLD: usize = 500;
impl LocalCoalescer {
pub fn build(ssa: &SsaFunction) -> Self {
if ssa.variable_count() > LINEAR_SCAN_THRESHOLD {
return Self::build_linear_scan(ssa);
}
Self::build_graph_coloring(ssa)
}
fn build_graph_coloring(ssa: &SsaFunction) -> Self {
let var_count = ssa.var_id_capacity();
let mut interference = InterferenceGraph::new(var_count);
for var in ssa.variables() {
interference.set_type(var.id(), var.var_type().clone());
}
let cfg = SsaCfg::from_ssa(ssa);
let analysis = LiveVariables::new(ssa);
let solver = DataFlowSolver::new(analysis);
let results = solver.solve(ssa, &cfg);
let block_ids: Vec<usize> = (0..ssa.block_count()).collect();
let boundary_edges: Vec<(SsaVarId, SsaVarId)> = block_ids
.par_iter()
.flat_map(|&block_id| {
let mut edges = Vec::new();
if let Some(live_out) = results.out_state(block_id) {
let live_vars: Vec<SsaVarId> = live_out.variables().collect();
for (i, &var1) in live_vars.iter().enumerate() {
for &var2 in &live_vars[i + 1..] {
edges.push((var1, var2));
}
}
}
if let Some(live_in) = results.in_state(block_id) {
let live_vars: Vec<SsaVarId> = live_in.variables().collect();
for (i, &var1) in live_vars.iter().enumerate() {
for &var2 in &live_vars[i + 1..] {
edges.push((var1, var2));
}
}
}
edges
})
.collect();
let intra_block_edges: Vec<(SsaVarId, SsaVarId)> = block_ids
.par_iter()
.flat_map(|&block_id| Self::collect_intra_block_edges(ssa, &results, block_id))
.collect();
let phi_edge_edges: Vec<(SsaVarId, SsaVarId)> = ssa
.blocks()
.par_iter()
.flat_map(|block| {
let mut edges = Vec::new();
let mut operands_by_pred: BTreeMap<usize, Vec<SsaVarId>> = BTreeMap::new();
for phi in block.phi_nodes() {
for operand in phi.operands() {
operands_by_pred
.entry(operand.predecessor())
.or_default()
.push(operand.value());
}
}
for (_, operands) in operands_by_pred {
for (i, &var1) in operands.iter().enumerate() {
for &var2 in &operands[i + 1..] {
edges.push((var1, var2));
}
}
}
edges
})
.collect();
let cross_subtree_edges: Vec<(SsaVarId, SsaVarId)> = block_ids
.par_iter()
.flat_map(|&block_id| Self::collect_cross_subtree_edges(ssa, block_id))
.collect();
for (a, b) in boundary_edges {
interference.add_edge(a, b);
}
for (a, b) in intra_block_edges {
interference.add_edge(a, b);
}
for (a, b) in phi_edge_edges {
interference.add_edge(a, b);
}
for (a, b) in cross_subtree_edges {
interference.add_edge(a, b);
}
let coalescable_vars: Vec<SsaVarId> = ssa
.variables()
.iter()
.filter_map(|v| match v.origin() {
VariableOrigin::Phi => Some(v.id()),
VariableOrigin::Argument(_) | VariableOrigin::Local(_) => None,
})
.collect();
Self {
interference,
coalescable_vars,
precomputed: None,
}
}
fn build_linear_scan(ssa: &SsaFunction) -> Self {
let mut var_types: BTreeMap<SsaVarId, SsaType> = BTreeMap::new();
for var in ssa.variables() {
var_types.insert(var.id(), var.var_type().clone());
}
let intervals = Self::compute_live_intervals(ssa);
let mut used_local_vars: Vec<(SsaVarId, u16)> = ssa
.variables()
.iter()
.filter_map(|v| {
if let VariableOrigin::Local(idx) = v.origin() {
if intervals.contains_key(&v.id()) {
return Some((v.id(), idx));
}
}
None
})
.collect();
used_local_vars.sort_by_key(|(_, idx)| *idx);
let PreAssignment {
mut var_to_local,
reserved_slots,
original_to_new,
mut next_local,
} = pre_assign_locals(ssa, &used_local_vars);
let mut sorted_intervals: Vec<_> = intervals.into_iter().collect();
sorted_intervals.sort_by(|(var_id_a, interval_a), (var_id_b, interval_b)| {
interval_a
.start
.cmp(&interval_b.start)
.then_with(|| var_sort_key(ssa, *var_id_a).cmp(&var_sort_key(ssa, *var_id_b)))
});
let mut active: BinaryHeap<Reverse<(usize, u16)>> = BinaryHeap::new();
let mut slot_type: BTreeMap<u16, SsaType> = BTreeMap::new();
let mut free_slots: Vec<(SsaType, u16)> = Vec::new();
for (var_id, interval) in sorted_intervals {
if var_to_local.contains_key(&var_id) {
if let Some(slot) = var_to_local.get(&var_id) {
let ty = var_types.get(&var_id).cloned().unwrap_or(SsaType::Unknown);
slot_type.insert(*slot, ty);
active.push(Reverse((interval.end, *slot)));
}
continue;
}
while let Some(Reverse((end, _slot))) = active.peek() {
if *end > interval.start {
break;
}
let Reverse((_, slot)) = active.pop().unwrap();
if !reserved_slots.contains(slot as usize) {
let ty = slot_type.get(&slot).cloned().unwrap_or(SsaType::Unknown);
free_slots.push((ty, slot));
}
}
let var_type = var_types.get(&var_id).cloned().unwrap_or(SsaType::Unknown);
let slot = {
let pick = free_slots
.iter()
.position(|(ty, _)| ty.is_compatible_for_storage(&var_type));
pick.map(|i| free_slots.swap_remove(i).1)
};
let slot = slot.unwrap_or_else(|| {
let s = next_local;
next_local += 1;
s
});
var_to_local.insert(var_id, slot);
slot_type.insert(slot, var_type.clone());
active.push(Reverse((interval.end, slot)));
}
let allocation = LocalAllocation {
var_to_local,
num_locals: next_local,
original_to_compacted: original_to_new,
};
Self {
interference: InterferenceGraph::new(0),
coalescable_vars: Vec::new(),
precomputed: Some(allocation),
}
}
fn compute_live_intervals(ssa: &SsaFunction) -> BTreeMap<SsaVarId, LiveInterval> {
let mut intervals: BTreeMap<SsaVarId, LiveInterval> = BTreeMap::new();
let mut block_end_idx: Vec<usize> = Vec::with_capacity(ssa.block_count());
{
let mut idx = 0usize;
for block_id in 0..ssa.block_count() {
if let Some(block) = ssa.block(block_id) {
idx += block.instructions().len();
}
block_end_idx.push(idx);
}
}
let mut instr_idx = 0usize;
for block_id in 0..ssa.block_count() {
let Some(block) = ssa.block(block_id) else {
continue;
};
for phi in block.phi_nodes() {
let def = phi.result();
intervals
.entry(def)
.or_insert_with(|| LiveInterval::new(instr_idx))
.extend_start(instr_idx);
for operand in phi.operands() {
let pred = operand.predecessor();
let pred_end = if pred < block_end_idx.len() {
block_end_idx[pred]
} else {
instr_idx + 1
};
let use_point = pred_end.max(instr_idx + 1);
intervals
.entry(operand.value())
.or_insert_with(|| LiveInterval::new(instr_idx))
.extend_end(use_point);
}
}
for instr in block.instructions() {
for &use_var in &instr.uses() {
intervals
.entry(use_var)
.or_insert_with(|| LiveInterval::new(instr_idx))
.extend_end(instr_idx + 1);
}
if let Some(def) = instr.def() {
intervals
.entry(def)
.or_insert_with(|| LiveInterval::new(instr_idx))
.extend_start(instr_idx);
}
instr_idx += 1;
}
}
intervals
}
fn collect_intra_block_edges(
ssa: &SsaFunction,
results: &AnalysisResults<LivenessResult>,
block_id: usize,
) -> Vec<(SsaVarId, SsaVarId)> {
let mut edges = Vec::new();
let var_count = ssa.var_id_capacity();
let Some(block) = ssa.block(block_id) else {
return edges;
};
let mut live = BitSet::new(var_count);
if let Some(live_out) = results.out_state(block_id) {
for var in live_out.variables() {
live.insert(var.index());
}
}
for instr in block.instructions().iter().rev() {
if let Some(def) = instr.def() {
for live_idx in live.iter() {
let live_var = SsaVarId::from_index(live_idx);
if live_var != def {
edges.push((def, live_var));
}
}
live.remove(def.index());
}
for &use_var in &instr.uses() {
live.insert(use_var.index());
}
}
for phi in block.phi_nodes() {
let def = phi.result();
for live_idx in live.iter() {
let live_var = SsaVarId::from_index(live_idx);
if live_var != def {
edges.push((def, live_var));
}
}
}
edges
}
fn collect_cross_subtree_edges(
ssa: &SsaFunction,
block_id: usize,
) -> Vec<(SsaVarId, SsaVarId)> {
let Some(block) = ssa.block(block_id) else {
return Vec::new();
};
let instructions = block.instructions();
if instructions.len() <= 1 {
return Vec::new();
}
let mut def_map: BTreeMap<SsaVarId, usize> = BTreeMap::new();
for (idx, instr) in instructions.iter().enumerate() {
if let Some(dest) = instr.def() {
def_map.insert(dest, idx);
}
}
let var_count = ssa.var_id_capacity();
let mut used_in_block = BitSet::new(var_count);
for instr in instructions {
for use_var in instr.uses() {
used_in_block.insert(use_var.index());
}
}
let roots: Vec<usize> = instructions
.iter()
.enumerate()
.filter_map(|(idx, instr)| {
let is_root = match instr.def() {
Some(dest) => {
!used_in_block.contains(dest.index())
|| matches!(
instr.op(),
SsaOp::Jump { .. }
| SsaOp::Branch { .. }
| SsaOp::BranchCmp { .. }
| SsaOp::Switch { .. }
| SsaOp::Return { .. }
| SsaOp::Throw { .. }
| SsaOp::Rethrow
| SsaOp::Leave { .. }
| SsaOp::EndFinally
| SsaOp::EndFilter { .. }
)
}
None => true,
};
if is_root {
Some(idx)
} else {
None
}
})
.collect();
if roots.len() <= 1 {
return Vec::new();
}
let mut instr_to_subtree: Vec<Option<usize>> = vec![None; instructions.len()];
for (subtree_id, &root_idx) in roots.iter().enumerate() {
let mut stack = vec![root_idx];
while let Some(idx) = stack.pop() {
if instr_to_subtree[idx].is_some() {
continue;
}
instr_to_subtree[idx] = Some(subtree_id);
for use_var in instructions[idx].uses() {
if let Some(&dep_idx) = def_map.get(&use_var) {
if instr_to_subtree[dep_idx].is_none() {
stack.push(dep_idx);
}
}
}
}
}
let num_subtrees = roots.len();
let mut subtree_vars: Vec<Vec<SsaVarId>> = vec![Vec::new(); num_subtrees];
for (idx, instr) in instructions.iter().enumerate() {
if let (Some(dest), Some(subtree_id)) = (instr.def(), instr_to_subtree[idx]) {
subtree_vars[subtree_id].push(dest);
}
}
let mut edges = Vec::new();
for i in 0..num_subtrees {
for j in (i + 1)..num_subtrees {
for &var_a in &subtree_vars[i] {
for &var_b in &subtree_vars[j] {
edges.push((var_a, var_b));
}
}
}
}
edges
}
pub fn allocate(&self, ssa: &SsaFunction) -> Result<LocalAllocation> {
if let Some(precomputed) = &self.precomputed {
return Ok(LocalAllocation {
var_to_local: precomputed.var_to_local.clone(),
num_locals: precomputed.num_locals,
original_to_compacted: precomputed.original_to_compacted.clone(),
});
}
self.allocate_graph_coloring(ssa)
}
fn allocate_graph_coloring(&self, ssa: &SsaFunction) -> Result<LocalAllocation> {
let var_count = ssa.variable_count();
let slot_capacity = var_count.max(self.coalescable_vars.len() + 1).max(64);
let mut used_local_var_ids = BitSet::new(slot_capacity);
for block in ssa.blocks() {
for phi in block.phi_nodes() {
if let Some(var) = ssa.variable(phi.result()) {
if matches!(var.origin(), VariableOrigin::Local(_)) {
used_local_var_ids.insert(phi.result().index());
}
}
for operand in phi.operands() {
if let Some(var) = ssa.variable(operand.value()) {
if matches!(var.origin(), VariableOrigin::Local(_)) {
used_local_var_ids.insert(operand.value().index());
}
}
}
}
for instr in block.instructions() {
let op = instr.op();
if let Some(dest) = op.dest() {
if let Some(var) = ssa.variable(dest) {
if matches!(var.origin(), VariableOrigin::Local(_)) {
used_local_var_ids.insert(dest.index());
}
}
}
for use_var in op.uses() {
if let Some(var) = ssa.variable(use_var) {
if matches!(var.origin(), VariableOrigin::Local(_)) {
used_local_var_ids.insert(use_var.index());
}
}
}
}
}
let mut local_vars: Vec<(SsaVarId, u16)> = ssa
.variables()
.iter()
.filter_map(|v| {
if let VariableOrigin::Local(idx) = v.origin() {
if used_local_var_ids.contains(v.id().index()) {
return Some((v.id(), idx));
}
}
None
})
.collect();
local_vars.sort_by_key(|(_, idx)| *idx);
let PreAssignment {
mut var_to_local,
reserved_slots,
original_to_new,
mut next_local,
} = pre_assign_locals(ssa, &local_vars);
let mut sorted_vars = self.coalescable_vars.clone();
sorted_vars.sort_by(|a, b| {
let deg_a = self.interference.degree(*a);
let deg_b = self.interference.degree(*b);
deg_b
.cmp(°_a)
.then_with(|| var_sort_key(ssa, *a).cmp(&var_sort_key(ssa, *b)))
});
for var in sorted_vars {
let mut used_slots = BitSet::new(slot_capacity);
for neighbor in self.interference.neighbors(var) {
if let Some(&slot) = var_to_local.get(&neighbor) {
used_slots.insert(slot as usize);
}
}
let var_type = self.interference.var_types.get(&var);
#[allow(clippy::maybe_infinite_iter)]
let slot = (0u16..)
.find(|&s| {
if reserved_slots.contains(s as usize) {
return false;
}
if used_slots.contains(s as usize) {
return false;
}
for (&other_var, &other_slot) in &var_to_local {
if other_slot == s {
let other_type = self.interference.var_types.get(&other_var);
if !types_compatible(var_type, other_type) {
return false;
}
}
}
true
})
.ok_or_else(|| Error::CodegenFailed("Should always find a valid slot".into()))?;
var_to_local.insert(var, slot);
next_local = next_local.max(slot + 1);
}
Ok(LocalAllocation {
var_to_local,
num_locals: next_local,
original_to_compacted: original_to_new,
})
}
}
struct PreAssignment {
var_to_local: BTreeMap<SsaVarId, u16>,
reserved_slots: BitSet,
original_to_new: BTreeMap<u16, u16>,
next_local: u16,
}
fn pre_assign_locals(ssa: &SsaFunction, used_local_vars: &[(SsaVarId, u16)]) -> PreAssignment {
let var_count = ssa.variable_count();
let slot_capacity = var_count.max(64);
let mut var_to_local: BTreeMap<SsaVarId, u16> = BTreeMap::new();
let mut reserved_slots = BitSet::new(slot_capacity);
let mut original_to_new: BTreeMap<u16, u16> = BTreeMap::new();
let mut next_local: u16 = 0;
for &(var_id, original_idx) in used_local_vars {
let new_slot = *original_to_new.entry(original_idx).or_insert_with(|| {
let slot = next_local;
next_local += 1;
slot
});
var_to_local.insert(var_id, new_slot);
reserved_slots.insert(new_slot as usize);
}
let mut load_referenced_locals = BitSet::new(slot_capacity);
for block in ssa.blocks() {
for instr in block.instructions() {
match instr.op() {
SsaOp::LoadLocal { local_index, .. } | SsaOp::LoadLocalAddr { local_index, .. } => {
load_referenced_locals.insert(*local_index as usize);
}
_ => {}
}
}
}
let mut sorted_load_refs: Vec<u16> = load_referenced_locals.iter().map(|i| i as u16).collect();
sorted_load_refs.sort_unstable();
for original_idx in sorted_load_refs {
original_to_new.entry(original_idx).or_insert_with(|| {
let slot = next_local;
next_local += 1;
reserved_slots.insert(slot as usize);
slot
});
}
PreAssignment {
var_to_local,
reserved_slots,
original_to_new,
next_local,
}
}
fn var_sort_key(ssa: &SsaFunction, var_id: SsaVarId) -> (VariableOrigin, u32) {
ssa.variable(var_id)
.map_or((VariableOrigin::Phi, u32::MAX), |v| {
(v.origin(), v.version())
})
}
fn types_compatible(t1: Option<&SsaType>, t2: Option<&SsaType>) -> bool {
match (t1, t2) {
(None, _) | (_, None) => true, (Some(a), Some(b)) => a.is_compatible_for_storage(b),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
use crate::analysis::{SsaFunctionBuilder, SsaType, SsaVarId};
fn make_vars(n: usize) -> Vec<SsaVarId> {
(0..n).map(SsaVarId::from_index).collect()
}
#[test]
fn test_interference_graph_add_edge() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(3);
let (var_a, var_b, var_c) = (vars[0], vars[1], vars[2]);
graph.add_edge(var_a, var_b);
assert_eq!(graph.degree(var_a), 1);
assert_eq!(graph.degree(var_b), 1);
assert_eq!(graph.degree(var_c), 0);
let neighbors_a: Vec<_> = graph.neighbors(var_a).collect();
assert_eq!(neighbors_a, vec![var_b]);
let neighbors_b: Vec<_> = graph.neighbors(var_b).collect();
assert_eq!(neighbors_b, vec![var_a]);
graph.add_edge(var_a, var_b);
assert_eq!(graph.degree(var_a), 1);
graph.add_edge(var_a, var_a);
assert_eq!(graph.degree(var_a), 1);
}
#[test]
fn test_interference_graph_multiple_edges() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(5);
graph.add_edge(vars[0], vars[1]);
graph.add_edge(vars[0], vars[2]);
graph.add_edge(vars[1], vars[2]);
assert_eq!(graph.degree(vars[0]), 2);
assert_eq!(graph.degree(vars[1]), 2);
assert_eq!(graph.degree(vars[2]), 2);
assert_eq!(graph.degree(vars[3]), 0);
assert_eq!(graph.degree(vars[4]), 0);
}
#[test]
fn test_type_compatibility_same_class() {
assert!(types_compatible(Some(&SsaType::I32), Some(&SsaType::I32)));
assert!(types_compatible(Some(&SsaType::I32), Some(&SsaType::U32)));
assert!(types_compatible(Some(&SsaType::I32), Some(&SsaType::Bool)));
assert!(types_compatible(Some(&SsaType::Bool), Some(&SsaType::Char)));
assert!(types_compatible(Some(&SsaType::I64), Some(&SsaType::U64)));
assert!(!types_compatible(
Some(&SsaType::Object),
Some(&SsaType::String)
));
assert!(types_compatible(
Some(&SsaType::String),
Some(&SsaType::String)
));
}
#[test]
fn test_type_compatibility_different_class() {
assert!(!types_compatible(Some(&SsaType::I32), Some(&SsaType::I64)));
assert!(!types_compatible(
Some(&SsaType::I32),
Some(&SsaType::Object)
));
assert!(!types_compatible(Some(&SsaType::F32), Some(&SsaType::F64)));
assert!(!types_compatible(Some(&SsaType::I32), Some(&SsaType::F32)));
}
#[test]
fn test_type_compatibility_with_none() {
assert!(types_compatible(None, Some(&SsaType::I32)));
assert!(types_compatible(Some(&SsaType::I32), None));
assert!(types_compatible(None, None));
}
#[test]
fn test_greedy_coloring_non_interfering_same_slot() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(2);
let (var_a, var_b) = (vars[0], vars[1]);
graph.set_type(var_a, SsaType::I32);
graph.set_type(var_b, SsaType::I32);
let coalescer = LocalCoalescer {
interference: graph,
coalescable_vars: vec![var_a, var_b],
precomputed: None,
};
let ssa = create_minimal_ssa_function();
let allocation = coalescer.allocate(&ssa).unwrap();
assert_eq!(allocation.var_to_local.get(&var_a), Some(&0));
assert_eq!(allocation.var_to_local.get(&var_b), Some(&0));
assert_eq!(allocation.num_locals, 1);
}
#[test]
fn test_greedy_coloring_interfering_different_slots() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(2);
let (var_a, var_b) = (vars[0], vars[1]);
graph.set_type(var_a, SsaType::I32);
graph.set_type(var_b, SsaType::I32);
graph.add_edge(var_a, var_b);
let coalescer = LocalCoalescer {
interference: graph,
coalescable_vars: vec![var_a, var_b],
precomputed: None,
};
let ssa = create_minimal_ssa_function();
let allocation = coalescer.allocate(&ssa).unwrap();
let slot_a = allocation.var_to_local.get(&var_a).unwrap();
let slot_b = allocation.var_to_local.get(&var_b).unwrap();
assert_ne!(slot_a, slot_b);
assert_eq!(allocation.num_locals, 2);
}
#[test]
fn test_greedy_coloring_type_incompatible_different_slots() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(2);
let (var_a, var_b) = (vars[0], vars[1]);
graph.set_type(var_a, SsaType::I32);
graph.set_type(var_b, SsaType::I64);
let coalescer = LocalCoalescer {
interference: graph,
coalescable_vars: vec![var_a, var_b],
precomputed: None,
};
let ssa = create_minimal_ssa_function();
let allocation = coalescer.allocate(&ssa).unwrap();
let slot_a = allocation.var_to_local.get(&var_a).unwrap();
let slot_b = allocation.var_to_local.get(&var_b).unwrap();
assert_ne!(slot_a, slot_b);
assert_eq!(allocation.num_locals, 2);
}
#[test]
fn test_greedy_coloring_clique_needs_n_colors() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(4);
for i in 0..4 {
graph.set_type(vars[i], SsaType::I32);
for j in (i + 1)..4 {
graph.add_edge(vars[i], vars[j]);
}
}
let coalescer = LocalCoalescer {
interference: graph,
coalescable_vars: vars.clone(),
precomputed: None,
};
let ssa = create_minimal_ssa_function();
let allocation = coalescer.allocate(&ssa).unwrap();
let slots: BTreeSet<_> = vars
.iter()
.filter_map(|v| allocation.var_to_local.get(v).copied())
.collect();
assert_eq!(slots.len(), 4);
assert_eq!(allocation.num_locals, 4);
}
#[test]
fn test_greedy_coloring_chain_needs_2_colors() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(4);
for var in &vars {
graph.set_type(*var, SsaType::I32);
}
graph.add_edge(vars[0], vars[1]);
graph.add_edge(vars[1], vars[2]);
graph.add_edge(vars[2], vars[3]);
let coalescer = LocalCoalescer {
interference: graph,
coalescable_vars: vars.clone(),
precomputed: None,
};
let ssa = create_minimal_ssa_function();
let allocation = coalescer.allocate(&ssa).unwrap();
for i in 0..3 {
let slot_i = allocation.var_to_local.get(&vars[i]).unwrap();
let slot_j = allocation.var_to_local.get(&vars[i + 1]).unwrap();
assert_ne!(
slot_i,
slot_j,
"Adjacent vars {} and {} share slot",
i,
i + 1
);
}
assert!(allocation.num_locals <= 2);
}
#[test]
fn test_mixed_types_coalesce_within_class() {
let mut graph = InterferenceGraph::new(10);
let vars = make_vars(5);
let (var_i32, var_u32, var_bool, var_i64, var_u64) =
(vars[0], vars[1], vars[2], vars[3], vars[4]);
graph.set_type(var_i32, SsaType::I32);
graph.set_type(var_u32, SsaType::U32);
graph.set_type(var_bool, SsaType::Bool);
graph.set_type(var_i64, SsaType::I64);
graph.set_type(var_u64, SsaType::U64);
let coalescer = LocalCoalescer {
interference: graph,
coalescable_vars: vec![var_i32, var_u32, var_bool, var_i64, var_u64],
precomputed: None,
};
let ssa = create_minimal_ssa_function();
let allocation = coalescer.allocate(&ssa).unwrap();
assert_eq!(allocation.var_to_local.get(&var_i32), Some(&0));
assert_eq!(allocation.var_to_local.get(&var_u32), Some(&0));
assert_eq!(allocation.var_to_local.get(&var_bool), Some(&0));
assert_eq!(allocation.var_to_local.get(&var_i64), Some(&1));
assert_eq!(allocation.var_to_local.get(&var_u64), Some(&1));
assert_eq!(allocation.num_locals, 2);
}
fn create_minimal_ssa_function() -> SsaFunction {
SsaFunctionBuilder::new(0, 0)
.build_with(|ctx| {
ctx.block(0, |b| {
b.ret();
});
})
.unwrap()
}
}