use std::cmp::Ordering;
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
pub type VReg = u32;
pub type PReg = u32;
pub type InstrIdx = u32;
pub type BlockId = u32;
pub type OpcodeTag = u32;
pub type LoopDepth = u32;
pub type SpillWeight = f64;
pub type PbqpCost = f64;
pub const PBQP_INF: PbqpCost = 1e12_f64;
pub const NO_REG: u32 = u32::MAX;
pub const X86_64_GPR_COUNT: usize = 16;
pub const X86_64_XMM_COUNT: usize = 16;
pub const VREG_BASE: VReg = 1024;
pub const MAX_PHYS_REGS: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MachineInstr {
pub idx: InstrIdx,
pub opcode: OpcodeTag,
pub defs: Vec<VReg>,
pub uses: Vec<VReg>,
pub copy: Option<(VReg, VReg)>,
pub block: BlockId,
pub is_terminator: bool,
pub is_call: bool,
pub loop_depth: LoopDepth,
pub mnemonic: String,
}
impl MachineInstr {
pub fn new(idx: InstrIdx, opcode: OpcodeTag, block: BlockId) -> Self {
MachineInstr {
idx,
opcode,
defs: Vec::new(),
uses: Vec::new(),
copy: None,
block,
is_terminator: false,
is_call: false,
loop_depth: 0,
mnemonic: String::new(),
}
}
pub fn def(mut self, vreg: VReg) -> Self {
self.defs.push(vreg);
self
}
pub fn use_(mut self, vreg: VReg) -> Self {
self.uses.push(vreg);
self
}
pub fn copy_from_to(mut self, src: VReg, dst: VReg) -> Self {
self.copy = Some((src, dst));
self
}
pub fn terminator(mut self) -> Self {
self.is_terminator = true;
self
}
pub fn call(mut self) -> Self {
self.is_call = true;
self
}
pub fn with_loop_depth(mut self, depth: LoopDepth) -> Self {
self.loop_depth = depth;
self
}
pub fn with_mnemonic(mut self, m: &str) -> Self {
self.mnemonic = m.to_string();
self
}
pub fn all_vregs(&self) -> impl Iterator<Item = VReg> + '_ {
self.defs.iter().chain(self.uses.iter()).copied()
}
}
#[derive(Debug, Clone)]
pub struct BasicBlock {
pub id: BlockId,
pub instrs: Vec<MachineInstr>,
pub preds: Vec<BlockId>,
pub succs: Vec<BlockId>,
pub loop_depth: LoopDepth,
}
impl BasicBlock {
pub fn new(id: BlockId) -> Self {
BasicBlock {
id,
instrs: Vec::new(),
preds: Vec::new(),
succs: Vec::new(),
loop_depth: 0,
}
}
pub fn add_pred(&mut self, pred: BlockId) {
if !self.preds.contains(&pred) {
self.preds.push(pred);
}
}
pub fn add_succ(&mut self, succ: BlockId) {
if !self.succs.contains(&succ) {
self.succs.push(succ);
}
}
}
#[derive(Debug, Clone)]
pub struct ControlFlowGraph {
pub blocks: Vec<BasicBlock>,
pub entry: BlockId,
pub exits: Vec<BlockId>,
pub rpo: Vec<BlockId>,
pub idom: Vec<BlockId>,
pub df: Vec<Vec<BlockId>>,
pub loop_headers: HashSet<BlockId>,
pub loop_depths: Vec<LoopDepth>,
}
impl ControlFlowGraph {
pub fn build(blocks: Vec<BasicBlock>, entry: BlockId) -> Self {
let n = blocks.len();
let mut cfg = ControlFlowGraph {
blocks,
entry,
exits: Vec::new(),
rpo: Vec::new(),
idom: vec![NO_REG as BlockId; n],
df: vec![Vec::new(); n],
loop_headers: HashSet::new(),
loop_depths: vec![0; n],
};
cfg.compute_rpo();
cfg.find_exits();
cfg.compute_dominators();
cfg.compute_dominance_frontiers();
cfg.identify_loops();
cfg.compute_loop_depths();
cfg
}
pub fn block(&self, id: BlockId) -> &BasicBlock {
&self.blocks[id as usize]
}
pub fn block_mut(&mut self, id: BlockId) -> &mut BasicBlock {
&mut self.blocks[id as usize]
}
pub fn num_blocks(&self) -> usize {
self.blocks.len()
}
fn compute_rpo(&mut self) {
let n = self.blocks.len();
let mut visited = vec![false; n];
let mut order = Vec::with_capacity(n);
fn dfs(
cfg: &ControlFlowGraph,
node: BlockId,
visited: &mut [bool],
order: &mut Vec<BlockId>,
) {
visited[node as usize] = true;
for &succ in &cfg.blocks[node as usize].succs {
if !visited[succ as usize] {
dfs(cfg, succ, visited, order);
}
}
order.push(node);
}
dfs(self, self.entry, &mut visited, &mut order);
order.reverse();
self.rpo = order;
}
fn find_exits(&mut self) {
self.exits = self
.blocks
.iter()
.filter(|b| b.succs.is_empty())
.map(|b| b.id)
.collect();
}
fn compute_dominators(&mut self) {
let n = self.blocks.len();
self.idom[self.entry as usize] = self.entry;
let mut changed = true;
let rpo_idx: HashMap<BlockId, usize> =
self.rpo.iter().enumerate().map(|(i, &b)| (b, i)).collect();
while changed {
changed = false;
for &b in &self.rpo {
if b == self.entry {
continue;
}
let preds = &self.blocks[b as usize].preds;
if preds.is_empty() {
continue;
}
let mut new_idom: Option<BlockId> = None;
for &p in preds {
if self.idom[p as usize] != NO_REG as BlockId {
new_idom = Some(p);
break;
}
}
if let Some(mut idom_candidate) = new_idom {
for &p in preds {
if p == idom_candidate {
continue;
}
if self.idom[p as usize] != NO_REG as BlockId {
idom_candidate = intersect_dom(&self.idom, &rpo_idx, p, idom_candidate);
}
}
if self.idom[b as usize] != idom_candidate {
self.idom[b as usize] = idom_candidate;
changed = true;
}
}
}
}
fn intersect_dom(
idom: &[BlockId],
rpo_idx: &HashMap<BlockId, usize>,
mut finger1: BlockId,
mut finger2: BlockId,
) -> BlockId {
while finger1 != finger2 {
let idx1 = rpo_idx.get(&finger1).copied().unwrap_or(usize::MAX);
let idx2 = rpo_idx.get(&finger2).copied().unwrap_or(usize::MAX);
while idx1 < idx2 {
finger1 = idom[finger1 as usize];
if finger1 == NO_REG as BlockId {
return finger2;
}
}
while idx2 < idx1 {
finger2 = idom[finger2 as usize];
if finger2 == NO_REG as BlockId {
return finger1;
}
}
}
finger1
}
}
fn compute_dominance_frontiers(&mut self) {
let n = self.blocks.len();
for b in 0..n as BlockId {
self.df[b as usize] = Vec::new();
}
for b in 0..n as BlockId {
let preds = self.blocks[b as usize].preds.clone();
if preds.len() >= 2 {
for &p in &preds {
let mut runner = p;
while runner != self.idom[b as usize] && runner != NO_REG as BlockId {
let df_entry = &mut self.df[runner as usize];
if !df_entry.contains(&b) {
df_entry.push(b);
}
runner = self.idom[runner as usize];
}
}
}
}
}
fn identify_loops(&mut self) {
let n = self.blocks.len();
for b in 0..n as BlockId {
for &succ in &self.blocks[b as usize].succs {
if self.dominates(succ, b) {
self.loop_headers.insert(succ);
}
}
}
}
pub fn dominates(&self, a: BlockId, b: BlockId) -> bool {
if a == b {
return true;
}
let mut curr = b;
loop {
let idom = self.idom[curr as usize];
if idom == NO_REG as BlockId {
return false;
}
if idom == a {
return true;
}
if idom == curr {
return false;
}
if idom >= self.blocks.len() as BlockId {
return false;
}
curr = idom;
}
}
fn compute_loop_depths(&mut self) {
let n = self.blocks.len();
let mut depth = vec![0u32; n];
for &header in &self.loop_headers {
let mut body: HashSet<BlockId> = HashSet::new();
body.insert(header);
let mut worklist: VecDeque<BlockId> = VecDeque::new();
for &pred in &self.blocks[header as usize].preds {
if self.dominates(header, pred) {
if body.insert(pred) {
worklist.push_back(pred);
}
}
}
while let Some(block) = worklist.pop_front() {
for &pred in &self.blocks[block as usize].preds {
if body.insert(pred) {
worklist.push_back(pred);
}
}
}
for &b in &body {
depth[b as usize] += 1;
}
}
self.loop_depths = depth;
for (b, &d) in self.loop_depths.iter().enumerate() {
self.blocks[b].loop_depth = d;
}
}
}
#[derive(Debug, Clone)]
pub struct LiveInterval {
pub vreg: VReg,
pub segments: Vec<LiveSegment>,
pub reg_class: RegClass,
pub weight: SpillWeight,
pub assigned_reg: Option<PReg>,
pub hint: Option<PReg>,
pub must_spill: bool,
pub def_point: Option<InstrIdx>,
}
impl LiveInterval {
pub fn new(vreg: VReg, reg_class: RegClass) -> Self {
LiveInterval {
vreg,
segments: Vec::new(),
reg_class,
weight: 0.0,
assigned_reg: None,
hint: None,
must_spill: false,
def_point: None,
}
}
pub fn add_segment(&mut self, start: InstrIdx, end: InstrIdx) {
if start >= end {
return;
}
self.segments.push(LiveSegment { start, end });
self.segments.sort_by_key(|s| s.start);
self.merge_segments();
}
fn merge_segments(&mut self) {
if self.segments.len() <= 1 {
return;
}
let mut merged: Vec<LiveSegment> = Vec::new();
let mut cur = self.segments[0];
for seg in &self.segments[1..] {
if seg.start <= cur.end {
cur.end = cur.end.max(seg.end);
} else {
merged.push(cur);
cur = *seg;
}
}
merged.push(cur);
self.segments = merged;
}
pub fn live_at(&self, point: InstrIdx) -> bool {
self.segments
.iter()
.any(|s| s.start <= point && point < s.end)
}
pub fn start(&self) -> InstrIdx {
self.segments.first().map(|s| s.start).unwrap_or(0)
}
pub fn end(&self) -> InstrIdx {
self.segments.last().map(|s| s.end).unwrap_or(0)
}
pub fn use_count(&self) -> u32 {
0
}
pub fn is_contiguous(&self) -> bool {
self.segments.len() <= 1
}
pub fn length(&self) -> u32 {
self.segments
.iter()
.map(|s| s.end.saturating_sub(s.start))
.sum()
}
pub fn segment_at(&self, point: InstrIdx) -> Option<&LiveSegment> {
self.segments
.iter()
.find(|s| s.start <= point && point < s.end)
}
pub fn split_at(&self, split_point: InstrIdx) -> (LiveInterval, LiveInterval) {
let mut before = LiveInterval::new(self.vreg, self.reg_class);
before.weight = self.weight;
before.hint = self.hint;
before.def_point = self.def_point;
before.assigned_reg = self.assigned_reg;
let mut after = LiveInterval::new(self.vreg + 1, self.reg_class); after.weight = self.weight * 0.5;
after.def_point = Some(split_point);
for seg in &self.segments {
if seg.end <= split_point {
before.segments.push(*seg);
} else if seg.start >= split_point {
after.segments.push(*seg);
} else {
before.segments.push(LiveSegment {
start: seg.start,
end: split_point,
});
after.segments.push(LiveSegment {
start: split_point,
end: seg.end,
});
}
}
before.merge_segments();
after.merge_segments();
(before, after)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LiveSegment {
pub start: InstrIdx,
pub end: InstrIdx,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegClass {
GPR,
XMM,
Mask,
Flags,
}
impl RegClass {
pub fn default_count(&self) -> usize {
match self {
RegClass::GPR => 16,
RegClass::XMM => 16,
RegClass::Mask => 8,
RegClass::Flags => 1,
}
}
pub fn has_aliases(&self) -> bool {
matches!(self, RegClass::GPR)
}
}
impl fmt::Display for RegClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RegClass::GPR => write!(f, "GPR"),
RegClass::XMM => write!(f, "XMM"),
RegClass::Mask => write!(f, "MASK"),
RegClass::Flags => write!(f, "FLAGS"),
}
}
}
#[derive(Debug, Clone)]
pub struct BlockLiveness {
pub live_in: HashSet<VReg>,
pub live_out: HashSet<VReg>,
pub def: HashSet<VReg>,
pub use_before_def: HashSet<VReg>,
pub upward_exposed: HashSet<VReg>,
pub live_out_defs: HashSet<VReg>,
}
impl BlockLiveness {
pub fn new() -> Self {
BlockLiveness {
live_in: HashSet::new(),
live_out: HashSet::new(),
def: HashSet::new(),
use_before_def: HashSet::new(),
upward_exposed: HashSet::new(),
live_out_defs: HashSet::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct LivenessAnalysis {
pub blocks: Vec<BlockLiveness>,
pub cfg: ControlFlowGraph,
pub num_vregs: usize,
pub def_sites: HashMap<VReg, Vec<InstrIdx>>,
pub use_sites: HashMap<VReg, Vec<InstrIdx>>,
pub all_vregs: HashSet<VReg>,
pub live_intervals: HashMap<VReg, LiveInterval>,
}
impl LivenessAnalysis {
pub fn new(cfg: ControlFlowGraph) -> Self {
let n = cfg.num_blocks();
LivenessAnalysis {
blocks: vec![BlockLiveness::new(); n],
cfg,
num_vregs: 0,
def_sites: HashMap::new(),
use_sites: HashMap::new(),
all_vregs: HashSet::new(),
live_intervals: HashMap::new(),
}
}
pub fn compute(&mut self) {
self.collect_def_use();
self.iterate_dataflow();
self.build_live_intervals();
}
fn collect_def_use(&mut self) {
let n = self.cfg.num_blocks();
for b in 0..n as BlockId {
let block = self.cfg.block(b);
let mut local_def: HashSet<VReg> = HashSet::new();
let mut local_use_before_def: HashSet<VReg> = HashSet::new();
let mut local_upward_exposed: HashSet<VReg> = HashSet::new();
for instr in &block.instrs {
for &u in &instr.uses {
self.use_sites.entry(u).or_default().push(instr.idx);
self.all_vregs.insert(u);
if !local_def.contains(&u) {
local_use_before_def.insert(u);
local_upward_exposed.insert(u);
}
}
for &d in &instr.defs {
self.def_sites.entry(d).or_default().push(instr.idx);
self.all_vregs.insert(d);
local_def.insert(d);
}
}
let bl = &mut self.blocks[b as usize];
bl.def = local_def;
bl.use_before_def = local_use_before_def;
bl.upward_exposed = local_upward_exposed;
}
self.num_vregs = self.all_vregs.len();
}
fn iterate_dataflow(&mut self) {
let n = self.cfg.num_blocks();
let mut changed = true;
let mut iteration = 0u32;
while changed {
changed = false;
iteration += 1;
for &b in self.cfg.rpo.iter().rev() {
let bl_idx = b as usize;
let mut new_live_out: HashSet<VReg> = HashSet::new();
for &succ in &self.cfg.blocks[bl_idx].succs {
for &vreg in &self.blocks[succ as usize].live_in {
new_live_out.insert(vreg);
}
}
let mut new_live_in: HashSet<VReg> = self.blocks[bl_idx].upward_exposed.clone();
for &vreg in &new_live_out {
if !self.blocks[bl_idx].def.contains(&vreg) {
new_live_in.insert(vreg);
}
}
if new_live_in != self.blocks[bl_idx].live_in
|| new_live_out != self.blocks[bl_idx].live_out
{
self.blocks[bl_idx].live_in = new_live_in;
self.blocks[bl_idx].live_out = new_live_out;
changed = true;
}
}
if iteration > 10000 {
break;
}
}
}
fn build_live_intervals(&mut self) {
for &vreg in &self.all_vregs {
let mut li = LiveInterval::new(vreg, RegClass::GPR);
if let Some(defs) = self.def_sites.get(&vreg) {
li.def_point = defs.first().copied();
}
self.live_intervals.insert(vreg, li);
}
let n = self.cfg.num_blocks();
for b in 0..n as BlockId {
let bl = &self.blocks[b as usize];
let block_instrs = &self.cfg.block(b).instrs;
if block_instrs.is_empty() {
continue;
}
let block_start = block_instrs.first().unwrap().idx;
let block_end = block_instrs.last().unwrap().idx + 1;
let mut live_in_additions: Vec<(VReg, InstrIdx, InstrIdx)> = Vec::new();
for &vreg in &bl.live_in {
let last_use = self.last_use_in_block(vreg, b);
let end = last_use.map(|u| u + 1).unwrap_or(block_end);
live_in_additions.push((vreg, block_start, end));
}
for (vreg, start, end) in live_in_additions {
if let Some(li) = self.live_intervals.get_mut(&vreg) {
li.add_segment(start, end);
}
}
let mut def_additions: Vec<(VReg, InstrIdx, InstrIdx)> = Vec::new();
for &vreg in &bl.live_out_defs {
let first_def = self.first_def_in_block(vreg, b);
let start = first_def.unwrap_or(block_start);
let last_use = self.last_use_in_block(vreg, b);
let end = last_use.map(|u| u + 1).unwrap_or(block_end);
def_additions.push((vreg, start, end));
}
for (vreg, start, end) in def_additions {
if let Some(li) = self.live_intervals.get_mut(&vreg) {
li.add_segment(start, end);
}
}
let mut gap_additions: Vec<(VReg, InstrIdx, InstrIdx)> = Vec::new();
for &vreg in &bl.live_in {
for &d in &bl.def {
if vreg == d {
let last_use = self.last_use_in_block(vreg, b);
let end = last_use.map(|u| u + 1).unwrap_or(block_end);
gap_additions.push((vreg, block_start, end));
}
}
}
for (vreg, start, end) in gap_additions {
if let Some(li) = self.live_intervals.get_mut(&vreg) {
li.add_segment(start, end);
}
}
}
for li in self.live_intervals.values_mut() {
if let Some(def_pt) = li.def_point {
li.segments.retain(|seg| seg.end > def_pt);
for seg in &mut li.segments {
if seg.start < def_pt {
seg.start = def_pt;
}
}
li.merge_segments();
}
}
}
fn first_def_in_block(&self, vreg: VReg, block: BlockId) -> Option<InstrIdx> {
self.cfg.block(block).instrs.iter().find_map(|instr| {
if instr.defs.contains(&vreg) {
Some(instr.idx)
} else {
None
}
})
}
fn last_use_in_block(&self, vreg: VReg, block: BlockId) -> Option<InstrIdx> {
self.cfg
.block(block)
.instrs
.iter()
.rev()
.find_map(|instr| {
if instr.uses.contains(&vreg) {
Some(instr.idx)
} else {
None
}
})
}
pub fn live_in(&self, block: BlockId) -> &HashSet<VReg> {
&self.blocks[block as usize].live_in
}
pub fn live_out(&self, block: BlockId) -> &HashSet<VReg> {
&self.blocks[block as usize].live_out
}
pub fn is_live_at(&self, vreg: VReg, point: InstrIdx) -> bool {
if let Some(li) = self.live_intervals.get(&vreg) {
li.live_at(point)
} else {
false
}
}
pub fn interfere(&self, a: VReg, b: VReg) -> bool {
let li_a = match self.live_intervals.get(&a) {
Some(li) => li,
None => return false,
};
let li_b = match self.live_intervals.get(&b) {
Some(li) => li,
None => return false,
};
Self::intervals_interfere(li_a, li_b)
}
pub fn intervals_interfere(a: &LiveInterval, b: &LiveInterval) -> bool {
let mut i = 0usize;
let mut j = 0usize;
while i < a.segments.len() && j < b.segments.len() {
let sa = &a.segments[i];
let sb = &b.segments[j];
if sa.end <= sb.start {
i += 1;
} else if sb.end <= sa.start {
j += 1;
} else {
return true;
}
}
false
}
pub fn live_regs_at(&self, point: InstrIdx) -> HashSet<VReg> {
let mut result = HashSet::new();
for (&vreg, li) in &self.live_intervals {
if li.live_at(point) {
result.insert(vreg);
}
}
result
}
}
#[derive(Debug, Clone)]
pub struct InterferenceNode {
pub vreg: VReg,
pub degree: u32,
pub reg_class: RegClass,
pub weight: SpillWeight,
pub removed: bool,
pub coalesced_into: Option<VReg>,
pub color: Option<PReg>,
pub hint: Option<PReg>,
pub live_interval: LiveInterval,
}
impl InterferenceNode {
pub fn new(vreg: VReg, reg_class: RegClass, live_interval: LiveInterval) -> Self {
InterferenceNode {
vreg,
degree: 0,
reg_class,
weight: 0.0,
removed: false,
coalesced_into: None,
color: None,
hint: None,
live_interval,
}
}
pub fn inc_degree(&mut self) {
self.degree += 1;
}
pub fn dec_degree(&mut self) {
self.degree = self.degree.saturating_sub(1);
}
}
#[derive(Debug, Clone)]
pub struct InterferenceGraph {
pub nodes: HashMap<VReg, InterferenceNode>,
pub adjacency: HashMap<VReg, HashSet<VReg>>,
pub matrix: HashMap<VReg, HashSet<VReg>>,
pub edge_count: usize,
liveness: Option<LivenessAnalysis>,
}
impl InterferenceGraph {
pub fn new() -> Self {
InterferenceGraph {
nodes: HashMap::new(),
adjacency: HashMap::new(),
matrix: HashMap::new(),
edge_count: 0,
liveness: None,
}
}
pub fn build_from_liveness(&mut self, liveness: &LivenessAnalysis) {
self.liveness = Some(liveness.clone());
for (&vreg, li) in &liveness.live_intervals {
let node = InterferenceNode::new(vreg, li.reg_class, li.clone());
self.nodes.insert(vreg, node);
self.adjacency.insert(vreg, HashSet::new());
self.matrix.insert(vreg, HashSet::new());
}
let vregs: Vec<VReg> = liveness.all_vregs.iter().copied().collect();
let n = vregs.len();
for i in 0..n {
let a = vregs[i];
for j in (i + 1)..n {
let b = vregs[j];
if liveness.interfere(a, b) {
self.add_edge(a, b);
}
}
}
}
pub fn build_from_liveness_sweep(&mut self, liveness: &LivenessAnalysis) {
self.liveness = Some(liveness.clone());
for (&vreg, li) in &liveness.live_intervals {
let node = InterferenceNode::new(vreg, li.reg_class, li.clone());
self.nodes.insert(vreg, node);
self.adjacency.insert(vreg, HashSet::new());
self.matrix.insert(vreg, HashSet::new());
}
#[derive(Debug, Clone, Copy)]
struct Event {
point: InstrIdx,
is_start: bool,
vreg: VReg,
}
let mut events: Vec<Event> = Vec::new();
for (&vreg, li) in &liveness.live_intervals {
for seg in &li.segments {
events.push(Event {
point: seg.start,
is_start: true,
vreg,
});
events.push(Event {
point: seg.end,
is_start: false,
vreg,
});
}
}
events.sort_by(|a, b| {
a.point
.cmp(&b.point)
.then_with(|| a.is_start.cmp(&b.is_start))
});
let mut active: HashSet<VReg> = HashSet::new();
for event in &events {
if event.is_start {
for &other in &active {
self.add_edge(event.vreg, other);
}
active.insert(event.vreg);
} else {
active.remove(&event.vreg);
}
}
}
pub fn add_edge(&mut self, a: VReg, b: VReg) {
if a == b {
return;
}
if let Some(neighbors) = self.adjacency.get_mut(&a) {
if neighbors.insert(b) {
self.edge_count += 1;
if let Some(node) = self.nodes.get_mut(&a) {
node.inc_degree();
}
}
}
if let Some(neighbors) = self.adjacency.get_mut(&b) {
neighbors.insert(a);
if let Some(node) = self.nodes.get_mut(&b) {
node.inc_degree();
}
}
if let Some(row) = self.matrix.get_mut(&a) {
row.insert(b);
}
if let Some(row) = self.matrix.get_mut(&b) {
row.insert(a);
}
}
pub fn remove_node(&mut self, vreg: VReg) {
if let Some(node) = self.nodes.get_mut(&vreg) {
node.removed = true;
}
if let Some(neighbors) = self.adjacency.get(&vreg) {
let neighbors_clone = neighbors.clone();
for &neighbor in &neighbors_clone {
self.remove_edge(vreg, neighbor);
}
}
}
pub fn remove_edge(&mut self, a: VReg, b: VReg) {
if let Some(neighbors) = self.adjacency.get_mut(&a) {
if neighbors.remove(&b) {
self.edge_count -= 1;
if let Some(node) = self.nodes.get_mut(&a) {
node.dec_degree();
}
}
}
if let Some(neighbors) = self.adjacency.get_mut(&b) {
neighbors.remove(&a);
if let Some(node) = self.nodes.get_mut(&b) {
node.dec_degree();
}
}
if let Some(row) = self.matrix.get_mut(&a) {
row.remove(&b);
}
if let Some(row) = self.matrix.get_mut(&b) {
row.remove(&a);
}
}
pub fn interfere(&self, a: VReg, b: VReg) -> bool {
if let Some(row) = self.matrix.get(&a) {
row.contains(&b)
} else {
false
}
}
pub fn degree(&self, vreg: VReg) -> u32 {
self.nodes.get(&vreg).map(|n| n.degree).unwrap_or(0)
}
pub fn neighbors(&self, vreg: VReg) -> Vec<&VReg> {
self.adjacency
.get(&vreg)
.map(|s| s.iter().collect())
.unwrap_or_default()
}
pub fn neighbor_count(&self, vreg: VReg) -> usize {
self.adjacency.get(&vreg).map(|s| s.len()).unwrap_or(0)
}
pub fn node(&self, vreg: VReg) -> Option<&InterferenceNode> {
self.nodes.get(&vreg)
}
pub fn node_mut(&mut self, vreg: VReg) -> Option<&mut InterferenceNode> {
self.nodes.get_mut(&vreg)
}
pub fn active_node_count(&self) -> usize {
self.nodes.values().filter(|n| !n.removed).count()
}
pub fn active_nodes_sorted(&self) -> Vec<&InterferenceNode> {
let mut nodes: Vec<&InterferenceNode> = self
.nodes
.values()
.filter(|n| !n.removed && n.coalesced_into.is_none())
.collect();
nodes.sort_by(|a, b| b.degree.cmp(&a.degree).then(b.weight.total_cmp(&a.weight)));
nodes
}
pub fn clique_lower_bound(&self) -> u32 {
let mut max_clique = 0u32;
for node in self.nodes.values() {
if node.removed || node.coalesced_into.is_some() {
continue;
}
let bound = node.degree + 1;
if bound > max_clique {
max_clique = bound;
}
}
max_clique
}
pub fn clear(&mut self) {
self.nodes.clear();
self.adjacency.clear();
self.matrix.clear();
self.edge_count = 0;
self.liveness = None;
}
}
impl Default for InterferenceGraph {
fn default() -> Self {
Self::new()
}
}
pub struct RegisterCoalescer {
pub ig: InterferenceGraph,
pub copy_pairs: Vec<(VReg, VReg)>,
pub coalesced: usize,
pub k_registers: usize,
pub strategy: CoalesceStrategy,
pub stats: CoalescerStats,
pub node_stack: Vec<VReg>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoalesceStrategy {
Conservative,
Aggressive,
Iterated,
}
#[derive(Debug, Clone, Default)]
pub struct CoalescerStats {
pub total_copies: usize,
pub briggs_coalesced: usize,
pub george_coalesced: usize,
pub failed: usize,
pub iterations: usize,
}
impl RegisterCoalescer {
pub fn new(ig: InterferenceGraph, k_registers: usize) -> Self {
RegisterCoalescer {
ig,
copy_pairs: Vec::new(),
coalesced: 0,
k_registers,
strategy: CoalesceStrategy::Conservative,
stats: CoalescerStats::default(),
node_stack: Vec::new(),
}
}
pub fn run(&mut self, strategy: CoalesceStrategy) -> usize {
self.strategy = strategy;
self.coalesced = 0;
match strategy {
CoalesceStrategy::Conservative => self.conservative_coalesce(),
CoalesceStrategy::Aggressive => self.aggressive_coalesce(),
CoalesceStrategy::Iterated => self.iterated_coalesce(),
}
self.coalesced
}
pub fn set_copy_pairs(&mut self, pairs: Vec<(VReg, VReg)>) {
self.copy_pairs = pairs;
self.stats.total_copies = self.copy_pairs.len();
}
fn conservative_coalesce(&mut self) {
let pairs = self.copy_pairs.clone();
let mut remaining: Vec<(VReg, VReg)> = Vec::new();
for &(u, v) in &pairs {
if u == v {
self.coalesced += 1;
continue;
}
if self.ig.interfere(u, v) {
if self.briggs_test(u, v) {
self.perform_coalesce(u, v);
self.stats.briggs_coalesced += 1;
self.coalesced += 1;
} else if self.george_test(u, v) {
self.perform_coalesce(u, v);
self.stats.george_coalesced += 1;
self.coalesced += 1;
} else {
self.stats.failed += 1;
remaining.push((u, v));
}
} else {
self.perform_coalesce(u, v);
self.coalesced += 1;
}
}
self.copy_pairs = remaining;
}
fn aggressive_coalesce(&mut self) {
for &(u, v) in &self.copy_pairs.clone() {
if u == v {
self.coalesced += 1;
continue;
}
if !self.ig.interfere(u, v) {
self.perform_coalesce(u, v);
self.coalesced += 1;
}
}
}
fn iterated_coalesce(&mut self) {
let mut changed = true;
while changed {
self.stats.iterations += 1;
if self.stats.iterations > 100 {
break;
}
let before = self.coalesced;
self.conservative_coalesce();
changed = self.coalesced > before;
}
}
fn briggs_test(&self, u: VReg, v: VReg) -> bool {
let neighbors_u: HashSet<VReg> = self.ig.adjacency.get(&u).cloned().unwrap_or_default();
let neighbors_v: HashSet<VReg> = self.ig.adjacency.get(&v).cloned().unwrap_or_default();
let merged: HashSet<VReg> = neighbors_u.union(&neighbors_v).copied().collect();
let significant = merged
.iter()
.filter(|&&n| n != u && n != v)
.filter(|&&n| self.ig.degree(n) >= self.k_registers as u32)
.count();
significant < self.k_registers
}
fn george_test(&self, u: VReg, v: VReg) -> bool {
let neighbors_u: Vec<VReg> = self
.ig
.adjacency
.get(&u)
.cloned()
.unwrap_or_default()
.into_iter()
.collect();
for t in neighbors_u {
if t == v {
continue;
}
if self.ig.interfere(t, v) {
continue;
}
if self.ig.degree(t) < self.k_registers as u32 {
continue;
}
return false;
}
true
}
fn perform_coalesce(&mut self, u: VReg, v: VReg) {
let v_neighbors: Vec<VReg> = self
.ig
.adjacency
.get(&v)
.cloned()
.unwrap_or_default()
.into_iter()
.collect();
for neighbor in v_neighbors {
if neighbor == u {
continue;
}
self.ig.remove_edge(v, neighbor);
if !self.ig.interfere(u, neighbor) {
self.ig.add_edge(u, neighbor);
}
}
self.ig.remove_edge(v, v);
if self.ig.interfere(u, v) {
self.ig.remove_edge(u, v);
}
if let Some(node) = self.ig.nodes.get_mut(&v) {
node.coalesced_into = Some(u);
}
if let Some(v_li) = self.ig.nodes.get(&v).map(|n| n.live_interval.clone()) {
if let Some(u_node) = self.ig.nodes.get_mut(&u) {
for seg in &v_li.segments {
u_node.live_interval.add_segment(seg.start, seg.end);
}
u_node.weight += v_li.weight;
}
}
}
pub fn can_coalesce(&self, u: VReg, v: VReg) -> bool {
if u == v {
return true;
}
if self.ig.interfere(u, v) {
self.briggs_test(u, v) || self.george_test(u, v)
} else {
true
}
}
}
pub struct SpillCost {
pub cfg: ControlFlowGraph,
pub weights: HashMap<VReg, SpillWeight>,
pub liveness: Option<LivenessAnalysis>,
pub loop_multiplier: f64,
pub normalize: bool,
}
impl SpillCost {
pub fn new(cfg: ControlFlowGraph) -> Self {
SpillCost {
cfg,
weights: HashMap::new(),
liveness: None,
loop_multiplier: 10.0,
normalize: false,
}
}
pub fn with_loop_multiplier(mut self, m: f64) -> Self {
self.loop_multiplier = m;
self
}
pub fn with_normalize(mut self, n: bool) -> Self {
self.normalize = n;
self
}
pub fn compute(&mut self, liveness: &LivenessAnalysis, ig: &InterferenceGraph) {
self.liveness = Some(liveness.clone());
self.weights.clear();
for &vreg in &liveness.all_vregs {
let weight = self.compute_weight_for(vreg, liveness, ig);
self.weights.insert(vreg, weight);
}
if self.normalize {
self.normalize_weights();
}
}
fn compute_weight_for(
&self,
vreg: VReg,
liveness: &LivenessAnalysis,
ig: &InterferenceGraph,
) -> SpillWeight {
let degree = ig.degree(vreg).max(1) as f64;
let mut total_weighted_uses = 0.0_f64;
for block_id in 0..self.cfg.num_blocks() as BlockId {
let block = self.cfg.block(block_id);
let freq = self.block_frequency(block_id);
let uses_in_block: u32 = block
.instrs
.iter()
.map(|instr| instr.uses.iter().filter(|&&u| u == vreg).count() as u32)
.sum();
total_weighted_uses += freq * uses_in_block as f64;
}
let has_def = liveness.def_sites.contains_key(&vreg);
if total_weighted_uses == 0.0 && !has_def {
return 0.0;
}
let weight = total_weighted_uses / degree;
if weight < 0.001 && has_def {
0.001 } else {
weight
}
}
fn block_frequency(&self, block_id: BlockId) -> f64 {
let depth = self.cfg.block(block_id).loop_depth;
self.loop_multiplier.powi(depth as i32)
}
fn normalize_weights(&mut self) {
let max_weight = self.weights.values().cloned().fold(0.0_f64, f64::max);
if max_weight > 0.0 {
for w in self.weights.values_mut() {
*w /= max_weight;
}
}
}
pub fn weight(&self, vreg: VReg) -> SpillWeight {
self.weights.get(&vreg).copied().unwrap_or(0.0)
}
pub fn sorted_by_weight(&self) -> Vec<(VReg, SpillWeight)> {
let mut pairs: Vec<(VReg, SpillWeight)> =
self.weights.iter().map(|(&v, &w)| (v, w)).collect();
pairs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
pairs
}
pub fn spill_cost(&self, vreg: VReg, liveness: &LivenessAnalysis) -> SpillWeight {
let use_sites = liveness.use_sites.get(&vreg);
let mut cost = 0.0_f64;
if let Some(sites) = use_sites {
for &instr_idx in sites {
if let Some(block_id) = self.find_block_for_instr(instr_idx) {
let freq = self.block_frequency(block_id);
cost += freq * 2.0;
}
}
}
cost
}
fn find_block_for_instr(&self, instr_idx: InstrIdx) -> Option<BlockId> {
for block_id in 0..self.cfg.num_blocks() as BlockId {
let block = self.cfg.block(block_id);
if let Some(first) = block.instrs.first() {
if let Some(last) = block.instrs.last() {
if first.idx <= instr_idx && instr_idx <= last.idx {
return Some(block_id);
}
}
}
}
None
}
}
pub struct GreedyAllocator {
pub ig: InterferenceGraph,
pub spill_cost: SpillCost,
pub liveness: LivenessAnalysis,
pub reg_file: RegisterFile,
pub assign_strategy: AssignStrategy,
pub enable_splitting: bool,
pub enable_eviction_chains: bool,
pub stats: GreedyStats,
pub node_stack: Vec<VReg>,
pub assignment: HashMap<VReg, PReg>,
}
#[derive(Debug, Clone, Default)]
pub struct GreedyStats {
pub total_vregs: usize,
pub assigned: usize,
pub spilled: usize,
pub split: usize,
pub evicted: usize,
pub coalesced: usize,
}
impl GreedyAllocator {
pub fn new(
liveness: LivenessAnalysis,
ig: InterferenceGraph,
spill_cost: SpillCost,
reg_file: RegisterFile,
) -> Self {
GreedyAllocator {
ig,
spill_cost,
liveness,
reg_file,
assign_strategy: AssignStrategy::BestFit,
enable_splitting: true,
enable_eviction_chains: true,
stats: GreedyStats::default(),
node_stack: Vec::new(),
assignment: HashMap::new(),
}
}
pub fn allocate(&mut self) -> &HashMap<VReg, PReg> {
self.stats = GreedyStats::default();
let sorted_vregs: Vec<VReg> = self
.spill_cost
.sorted_by_weight()
.into_iter()
.map(|(v, _)| v)
.collect();
self.stats.total_vregs = sorted_vregs.len();
for &vreg in &sorted_vregs {
self.allocate_vreg(vreg);
}
&self.assignment
}
fn allocate_vreg(&mut self, vreg: VReg) -> bool {
let hint = self.ig.nodes.get(&vreg).and_then(|n| n.hint);
let reg_class = self
.ig
.nodes
.get(&vreg)
.map(|n| n.reg_class)
.unwrap_or(RegClass::GPR);
let allocated = match self.assign_strategy {
AssignStrategy::FirstFit => self.try_first_fit(vreg, hint, reg_class),
AssignStrategy::BestFit => self.try_best_fit(vreg, hint, reg_class),
AssignStrategy::HintAware => self.try_hint_aware(vreg, hint, reg_class),
};
if allocated {
self.stats.assigned += 1;
return true;
}
if self.enable_eviction_chains {
if self.try_evict(vreg, reg_class) {
self.stats.assigned += 1;
return true;
}
}
if self.enable_splitting {
if self.try_split(vreg) {
self.stats.split += 1;
self.stats.assigned += 1;
return true;
}
}
self.stats.spilled += 1;
false
}
fn try_first_fit(&mut self, vreg: VReg, hint: Option<PReg>, reg_class: RegClass) -> bool {
let available = self.reg_file.registers_of_class(reg_class);
if let Some(h) = hint {
if available.contains(&h) && self.is_reg_free(h, vreg) {
self.assign(vreg, h);
return true;
}
}
for &preg in &available {
if self.is_reg_free(preg, vreg) {
self.assign(vreg, preg);
return true;
}
}
false
}
fn try_best_fit(&mut self, vreg: VReg, hint: Option<PReg>, reg_class: RegClass) -> bool {
let available = self.reg_file.registers_of_class(reg_class);
if let Some(h) = hint {
if available.contains(&h) && self.is_reg_free(h, vreg) {
self.assign(vreg, h);
return true;
}
}
let mut best_reg: Option<PReg> = None;
let mut best_conflicts = u32::MAX;
for &preg in &available {
if !self.is_reg_free(preg, vreg) {
continue;
}
let conflicts = self.count_future_conflicts(vreg, preg);
if conflicts < best_conflicts {
best_conflicts = conflicts;
best_reg = Some(preg);
}
}
if let Some(preg) = best_reg {
self.assign(vreg, preg);
return true;
}
false
}
fn try_hint_aware(&mut self, vreg: VReg, hint: Option<PReg>, reg_class: RegClass) -> bool {
if let Some(h) = hint {
let available = self.reg_file.registers_of_class(reg_class);
if available.contains(&h) {
if !self.is_reg_free(h, vreg) {
if let Some(occupant) = self.find_occupant(h) {
let occ_weight = self.spill_cost.weight(occupant);
let my_weight = self.spill_cost.weight(vreg);
if my_weight > occ_weight {
self.evict(occupant);
self.assign(vreg, h);
self.stats.evicted += 1;
return true;
}
}
} else {
self.assign(vreg, h);
return true;
}
}
}
self.try_best_fit(vreg, hint, reg_class)
}
fn try_evict(&mut self, vreg: VReg, reg_class: RegClass) -> bool {
let my_weight = self.spill_cost.weight(vreg);
let available = self.reg_file.registers_of_class(reg_class);
let mut candidates: Vec<(PReg, VReg, SpillWeight)> = Vec::new();
for &preg in &available {
if !self.is_reg_free(preg, vreg) {
if let Some(occupant) = self.find_occupant(preg) {
let occ_weight = self.spill_cost.weight(occupant);
candidates.push((preg, occupant, occ_weight));
}
}
}
candidates.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Ordering::Equal));
for (preg, occupant, occ_weight) in &candidates {
if my_weight > *occ_weight {
self.evict(*occupant);
self.assign(vreg, *preg);
self.stats.evicted += 1;
return true;
}
}
false
}
fn try_split(&mut self, vreg: VReg) -> bool {
if let Some(split_point) = self.find_split_point(vreg) {
let li = match self.ig.nodes.get(&vreg) {
Some(n) => n.live_interval.clone(),
None => return false,
};
let (before, _after) = li.split_at(split_point);
if let Some(node) = self.ig.nodes.get_mut(&vreg) {
node.live_interval = before.clone();
}
let reg_class = self
.ig
.nodes
.get(&vreg)
.map(|n| n.reg_class)
.unwrap_or(RegClass::GPR);
if let Some(hint) = self.ig.nodes.get(&vreg).and_then(|n| n.hint) {
self.try_best_fit(vreg, Some(hint), reg_class)
} else {
self.try_best_fit(vreg, None, reg_class)
}
} else {
false
}
}
fn find_split_point(&self, vreg: VReg) -> Option<InstrIdx> {
let li = match self.ig.nodes.get(&vreg) {
Some(n) => &n.live_interval,
None => return None,
};
if li.segments.is_empty() {
return None;
}
let mut best_point: Option<InstrIdx> = None;
let mut min_live_count = usize::MAX;
let start = li.start();
let end = li.end();
for point in start..end {
if !li.live_at(point) {
continue;
}
let live_count = self.liveness.live_regs_at(point).len();
if live_count < min_live_count {
min_live_count = live_count;
best_point = Some(point);
}
}
best_point
}
fn assign(&mut self, vreg: VReg, preg: PReg) {
self.assignment.insert(vreg, preg);
self.reg_file.occupy(preg, vreg);
if let Some(node) = self.ig.nodes.get_mut(&vreg) {
node.color = Some(preg);
}
}
fn evict(&mut self, vreg: VReg) {
if let Some(&preg) = self.assignment.get(&vreg) {
self.reg_file.release(preg);
}
self.assignment.remove(&vreg);
if let Some(node) = self.ig.nodes.get_mut(&vreg) {
node.color = None;
}
}
fn is_reg_free(&self, preg: PReg, vreg: VReg) -> bool {
if let Some(occupant) = self.find_occupant(preg) {
if occupant == vreg {
return true; }
!self.ig.interfere(vreg, occupant)
} else {
true
}
}
fn find_occupant(&self, preg: PReg) -> Option<VReg> {
self.reg_file.occupant(preg)
}
fn count_future_conflicts(&self, vreg: VReg, preg: PReg) -> u32 {
let mut conflicts = 0u32;
if let Some(neighbors) = self.ig.adjacency.get(&vreg) {
for &neighbor in neighbors {
if !self.assignment.contains_key(&neighbor) {
conflicts += 1;
} else if self.assignment.get(&neighbor) == Some(&preg) {
conflicts += 3;
}
}
}
conflicts
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssignStrategy {
FirstFit,
BestFit,
HintAware,
}
pub struct LinearScanAllocator {
pub intervals: Vec<LiveInterval>,
pub k_registers: usize,
pub active: Vec<LiveInterval>,
pub assignment: HashMap<VReg, PReg>,
pub spills: HashSet<VReg>,
pub free_regs: Vec<PReg>,
pub reg_class: RegClass,
pub stats: LinearScanStats,
}
#[derive(Debug, Clone, Default)]
pub struct LinearScanStats {
pub total_vregs: usize,
pub assigned: usize,
pub spilled: usize,
pub expired_intervals: usize,
}
impl LinearScanAllocator {
pub fn new(k_registers: usize, reg_class: RegClass) -> Self {
LinearScanAllocator {
intervals: Vec::new(),
k_registers,
active: Vec::new(),
assignment: HashMap::new(),
spills: HashSet::new(),
free_regs: (0..k_registers as u32).collect(),
reg_class,
stats: LinearScanStats::default(),
}
}
pub fn set_intervals(&mut self, intervals: Vec<LiveInterval>) {
self.intervals = intervals;
}
pub fn allocate(&mut self) {
self.stats = LinearScanStats::default();
self.assignment.clear();
self.spills.clear();
self.active.clear();
self.free_regs = (0..self.k_registers as u32).collect();
self.stats.total_vregs = self.intervals.len();
self.intervals.sort_by_key(|li| li.start());
for i in 0..self.intervals.len() {
let cur_start = self.intervals[i].start();
let cur_vreg = self.intervals[i].vreg;
let cur_end = self.intervals[i].end();
self.expire_old_intervals(cur_start);
if self.active.len() < self.k_registers {
let preg = self.free_regs.pop().expect("free_regs should not be empty");
self.assignment.insert(cur_vreg, preg);
self.stats.assigned += 1;
self.active.push(self.intervals[i].clone());
self.active.sort_by_key(|li| li.end());
} else {
let last_active = self.active.last().expect("active list should not be empty");
let last_end = last_active.end();
if last_end > cur_end {
let spilled_li = self.active.pop().unwrap();
self.spills.insert(spilled_li.vreg);
self.stats.spilled += 1;
if let Some(&preg) = self.assignment.get(&spilled_li.vreg) {
self.assignment.remove(&spilled_li.vreg);
self.assignment.insert(cur_vreg, preg);
self.stats.assigned += 1;
}
self.active.push(self.intervals[i].clone());
self.active.sort_by_key(|li| li.end());
} else {
self.spills.insert(cur_vreg);
self.stats.spilled += 1;
}
}
}
}
fn expire_old_intervals(&mut self, position: InstrIdx) {
let mut expired_count = 0usize;
let mut remaining: Vec<LiveInterval> = Vec::new();
for li in self.active.drain(..) {
if li.end() <= position {
if let Some(&preg) = self.assignment.get(&li.vreg) {
self.free_regs.push(preg);
}
expired_count += 1;
} else {
remaining.push(li);
}
}
self.active = remaining;
self.active.sort_by_key(|li| li.end());
self.stats.expired_intervals += expired_count;
}
pub fn is_spilled(&self, vreg: VReg) -> bool {
self.spills.contains(&vreg)
}
pub fn get_assignment(&self) -> &HashMap<VReg, PReg> {
&self.assignment
}
pub fn allocate_with_second_chance(&mut self) {
self.allocate();
let spilled_list: Vec<LiveInterval> = self
.intervals
.iter()
.filter(|li| self.spills.contains(&li.vreg))
.cloned()
.collect();
for li in &spilled_list {
for &preg in &self.free_regs.clone() {
if self.is_register_free_during(preg, li) {
self.assignment.insert(li.vreg, preg);
self.spills.remove(&li.vreg);
self.stats.spilled -= 1;
self.stats.assigned += 1;
break;
}
}
}
}
fn is_register_free_during(&self, preg: PReg, li: &LiveInterval) -> bool {
for (vreg, &assigned_preg) in &self.assignment {
if assigned_preg != preg {
continue;
}
if let Some(other_li) = self.intervals.iter().find(|x| x.vreg == *vreg) {
if LivenessAnalysis::intervals_interfere(li, other_li) {
return false;
}
}
}
true
}
}
#[derive(Debug, Clone)]
pub struct PbqpMatrix {
pub data: Vec<Vec<PbqpCost>>,
pub rows: usize,
pub cols: usize,
}
impl PbqpMatrix {
pub fn new(rows: usize, cols: usize, default_cost: PbqpCost) -> Self {
PbqpMatrix {
data: vec![vec![default_cost; cols]; rows],
rows,
cols,
}
}
pub fn coalescing_matrix(rows: usize, cols: usize, benefit: PbqpCost) -> Self {
let mut matrix = Self::new(rows, cols, 0.0);
let n = rows.min(cols);
for i in 0..n {
matrix.set(i as usize, i as usize, -benefit);
}
matrix
}
pub fn interference_matrix(rows: usize, cols: usize) -> Self {
let mut matrix = Self::new(rows, cols, 0.0);
let n = rows.min(cols);
for i in 0..n {
matrix.set(i as usize, i as usize, PBQP_INF);
}
matrix
}
pub fn set(&mut self, row: usize, col: usize, cost: PbqpCost) {
if row < self.rows && col < self.cols {
self.data[row][col] = cost;
}
}
pub fn get(&self, row: usize, col: usize) -> PbqpCost {
if row < self.rows && col < self.cols {
self.data[row][col]
} else {
PBQP_INF
}
}
pub fn col_min(&self, col: usize) -> PbqpCost {
if col >= self.cols {
return PBQP_INF;
}
self.data
.iter()
.map(|row| row[col])
.fold(PBQP_INF, f64::min)
}
pub fn row_min(&self, row: usize) -> PbqpCost {
if row >= self.rows {
return PBQP_INF;
}
self.data[row].iter().fold(PBQP_INF, |a, &b| a.min(b))
}
pub fn subtract_cols(&mut self, col_delta: &[PbqpCost]) {
for row in self.data.iter_mut() {
for (j, &delta) in col_delta.iter().enumerate() {
if j < row.len() {
row[j] -= delta;
}
}
}
}
pub fn subtract_rows(&mut self, row_delta: &[PbqpCost]) {
for (i, row) in self.data.iter_mut().enumerate() {
if let Some(&delta) = row_delta.get(i) {
for val in row.iter_mut() {
*val -= delta;
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct PbqpNode {
pub vreg: VReg,
pub costs: PbqpVector,
pub reg_class: RegClass,
pub eliminated: bool,
pub eliminated_by: Option<PbqpReduction>,
pub neighbors: HashSet<VReg>,
pub solution: Option<PReg>,
}
impl PbqpNode {
pub fn new(vreg: VReg, reg_class: RegClass, allowed_regs: &[PReg]) -> Self {
PbqpNode {
vreg,
costs: PbqpVector::new(allowed_regs, 0.0),
reg_class,
eliminated: false,
eliminated_by: None,
neighbors: HashSet::new(),
solution: None,
}
}
}
#[derive(Debug, Clone)]
pub struct PbqpVector {
pub costs: Vec<PbqpCost>,
pub reg_to_idx: HashMap<PReg, usize>,
pub idx_to_reg: Vec<PReg>,
}
impl PbqpVector {
pub fn new(allowed_regs: &[PReg], default_cost: PbqpCost) -> Self {
let mut reg_to_idx = HashMap::new();
let mut idx_to_reg = Vec::new();
let mut costs = Vec::new();
for (i, ®) in allowed_regs.iter().enumerate() {
reg_to_idx.insert(reg, i);
idx_to_reg.push(reg);
costs.push(default_cost);
}
PbqpVector {
costs,
reg_to_idx,
idx_to_reg,
}
}
pub fn get_cost(&self, reg: PReg) -> Option<PbqpCost> {
self.reg_to_idx.get(®).map(|&i| self.costs[i])
}
pub fn set_cost(&mut self, reg: PReg, cost: PbqpCost) {
if let Some(&i) = self.reg_to_idx.get(®) {
self.costs[i] = cost;
}
}
pub fn add_cost(&mut self, reg: PReg, cost: PbqpCost) {
if let Some(&i) = self.reg_to_idx.get(®) {
self.costs[i] += cost;
}
}
pub fn min_cost_reg(&self) -> Option<(PReg, PbqpCost)> {
let mut best: Option<(usize, PbqpCost)> = None;
for (i, &c) in self.costs.iter().enumerate() {
match best {
None => best = Some((i, c)),
Some((_, prev)) if c < prev => best = Some((i, c)),
_ => {}
}
}
best.map(|(i, cost)| (self.idx_to_reg[i], cost))
}
pub fn has_finite_cost(&self) -> bool {
self.costs.iter().any(|&c| c < PBQP_INF / 2.0)
}
pub fn len(&self) -> usize {
self.costs.len()
}
pub fn is_empty(&self) -> bool {
self.costs.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PbqpReduction {
R0,
R1,
R2,
RN,
}
#[derive(Debug, Clone)]
pub struct PbqpGraph {
pub nodes: HashMap<VReg, PbqpNode>,
pub edges: HashMap<(VReg, VReg), PbqpMatrix>,
pub reduction_stack: Vec<(VReg, PbqpReduction, Option<PbqpVector>)>,
pub stats: PbqpStats,
pub k_registers: usize,
}
#[derive(Debug, Clone, Default)]
pub struct PbqpStats {
pub total_nodes: usize,
pub r0_reductions: usize,
pub r1_reductions: usize,
pub r2_reductions: usize,
pub rn_reductions: usize,
pub optimal_solutions: usize,
pub infeasible_nodes: usize,
}
impl PbqpGraph {
pub fn new(k_registers: usize) -> Self {
PbqpGraph {
nodes: HashMap::new(),
edges: HashMap::new(),
reduction_stack: Vec::new(),
stats: PbqpStats::default(),
k_registers,
}
}
pub fn add_node(&mut self, vreg: VReg, reg_class: RegClass) {
let allowed: Vec<PReg> = (0..self.k_registers as PReg).collect();
let node = PbqpNode::new(vreg, reg_class, &allowed);
self.nodes.insert(vreg, node);
self.stats.total_nodes += 1;
}
pub fn add_edge(&mut self, u: VReg, v: VReg, matrix: PbqpMatrix) {
if u == v {
return;
}
self.edges.insert((u.min(v), u.max(v)), matrix);
if let Some(node) = self.nodes.get_mut(&u) {
node.neighbors.insert(v);
}
if let Some(node) = self.nodes.get_mut(&v) {
node.neighbors.insert(u);
}
}
pub fn edge_matrix(&self, u: VReg, v: VReg) -> Option<&PbqpMatrix> {
let key = (u.min(v), u.max(v));
self.edges.get(&key)
}
pub fn solve(&mut self) {
self.reduce();
self.backpropagate();
}
fn reduce(&mut self) {
loop {
let reduced = self.try_reduce_r0()
|| self.try_reduce_rn()
|| self.try_reduce_r1()
|| self.try_reduce_r2();
if !reduced {
break;
}
}
while !self.nodes.is_empty() {
self.heuristic_reduce();
}
}
fn try_reduce_r0(&mut self) -> bool {
let candidates: Vec<VReg> = self
.nodes
.iter()
.filter(|(_, n)| !n.eliminated && n.costs.len() == 1)
.map(|(&v, _)| v)
.collect();
if candidates.is_empty() {
return false;
}
for vreg in candidates {
let node = self.nodes.remove(&vreg).unwrap();
let neighbors: Vec<VReg> = node.neighbors.iter().copied().collect();
for neighbor in &neighbors {
let key = (vreg.min(*neighbor), vreg.max(*neighbor));
if let Some(matrix) = self.edges.remove(&key) {
let updates: Vec<(PReg, PbqpCost)> = {
if let Some(nbr_node) = self.nodes.get(neighbor) {
let row_idx = 0usize;
nbr_node
.costs
.idx_to_reg
.iter()
.enumerate()
.map(|(col_idx, ®)| (reg, matrix.get(row_idx, col_idx)))
.collect()
} else {
Vec::new()
}
};
if let Some(nbr_node) = self.nodes.get_mut(neighbor) {
for (reg, cost) in updates {
nbr_node.costs.add_cost(reg, cost);
}
}
}
if let Some(nbr_node) = self.nodes.get_mut(neighbor) {
nbr_node.neighbors.remove(&vreg);
}
}
self.reduction_stack.push((vreg, PbqpReduction::R0, None));
self.stats.r0_reductions += 1;
}
true
}
fn try_reduce_rn(&mut self) -> bool {
let mut reduced = false;
let vregs: Vec<VReg> = self.nodes.keys().copied().collect();
for &vreg in &vregs {
if !self.nodes.contains_key(&vreg) {
continue;
}
let n_choices = {
if let Some(node) = self.nodes.get(&vreg) {
node.costs.len()
} else {
continue;
}
};
if n_choices <= 1 {
continue;
}
let mut to_remove: Vec<usize> = Vec::new();
for i in 0..n_choices {
for j in 0..n_choices {
if i == j {
continue;
}
let node = self.nodes.get(&vreg).unwrap();
let cost_i = node.costs.costs[i];
let cost_j = node.costs.costs[j];
if cost_i < cost_j {
continue; }
let mut i_dominates = true;
let neighbors: Vec<VReg> = node.neighbors.iter().copied().collect();
for &neighbor in &neighbors {
let key = (vreg.min(neighbor), vreg.max(neighbor));
if let Some(matrix) = self.edges.get(&key) {
let (vreg_is_row, vreg_idx) = if key.0 == vreg {
(true, i)
} else {
(false, i)
};
let (_, other_idx) = if key.0 == vreg { (true, j) } else { (false, j) };
let nbr_node = self.nodes.get(&neighbor).unwrap();
for col in 0..nbr_node.costs.len() {
let cost_with_i = if vreg_is_row {
matrix.get(vreg_idx, col)
} else {
matrix.get(col, vreg_idx)
};
let cost_with_j = if vreg_is_row {
matrix.get(other_idx, col)
} else {
matrix.get(col, other_idx)
};
if cost_with_i > cost_with_j {
i_dominates = false;
break;
}
}
if !i_dominates {
break;
}
}
}
if i_dominates && !to_remove.contains(&i) {
to_remove.push(i);
break; }
}
}
if !to_remove.is_empty() {
to_remove.sort_unstable();
to_remove.reverse();
if let Some(node) = self.nodes.get_mut(&vreg) {
for &idx in &to_remove {
node.costs.costs.remove(idx);
let removed_reg = node.costs.idx_to_reg.remove(idx);
node.costs.reg_to_idx.remove(&removed_reg);
for (new_idx, ®) in node.costs.idx_to_reg.iter().enumerate() {
node.costs.reg_to_idx.insert(reg, new_idx);
}
}
}
reduced = true;
self.stats.rn_reductions += to_remove.len();
}
}
reduced
}
fn try_reduce_r1(&mut self) -> bool {
let candidates: Vec<VReg> = self
.nodes
.iter()
.filter(|(_, n)| !n.eliminated && n.neighbors.len() == 1)
.map(|(&v, _)| v)
.collect();
if candidates.is_empty() {
return false;
}
for vreg in candidates {
if !self.nodes.contains_key(&vreg) {
continue;
}
let node = self.nodes.get(&vreg).cloned().unwrap();
let neighbor = *node.neighbors.iter().next().unwrap();
let key = (vreg.min(neighbor), vreg.max(neighbor));
if let Some(matrix) = self.edges.remove(&key) {
if let Some(nbr_node) = self.nodes.get_mut(&neighbor) {
let mut new_costs: Vec<PbqpCost> = vec![0.0; nbr_node.costs.len()];
for (nbr_idx, &nbr_reg) in nbr_node.costs.idx_to_reg.iter().enumerate() {
let mut min_total = PBQP_INF;
for (vreg_idx, _) in node.costs.idx_to_reg.iter().enumerate() {
let is_row_first = key.0 == vreg;
let edge_cost = if is_row_first {
matrix.get(vreg_idx, nbr_idx)
} else {
matrix.get(nbr_idx, vreg_idx)
};
let total = node.costs.costs[vreg_idx] + edge_cost;
if total < min_total {
min_total = total;
}
}
new_costs[nbr_idx] = nbr_node.costs.costs[nbr_idx] + min_total;
}
nbr_node.costs.costs = new_costs;
nbr_node.neighbors.remove(&vreg);
}
}
self.nodes.remove(&vreg);
self.reduction_stack.push((vreg, PbqpReduction::R1, None));
self.stats.r1_reductions += 1;
}
true
}
fn try_reduce_r2(&mut self) -> bool {
let candidates: Vec<VReg> = self
.nodes
.iter()
.filter(|(_, n)| !n.eliminated && n.neighbors.len() == 2)
.map(|(&v, _)| v)
.collect();
let had_candidates = !candidates.is_empty();
if !had_candidates {
return false;
}
for vreg in candidates {
if !self.nodes.contains_key(&vreg) {
continue;
}
let node = self.nodes.get(&vreg).cloned().unwrap();
let neighbors: Vec<VReg> = node.neighbors.iter().copied().collect();
let v = neighbors[0];
let w = neighbors[1];
let key_uv = (vreg.min(v), vreg.max(v));
let key_uw = (vreg.min(w), vreg.max(w));
let matrix_uv = match self.edges.remove(&key_uv) {
Some(m) => m,
None => continue,
};
let matrix_uw = match self.edges.remove(&key_uw) {
Some(m) => m,
None => {
self.edges.insert(key_uv, matrix_uv);
continue;
}
};
let v_node = self.nodes.get(&v).cloned().unwrap();
let w_node = self.nodes.get(&w).cloned().unwrap();
let mut new_matrix = PbqpMatrix::new(v_node.costs.len(), w_node.costs.len(), 0.0);
for (vi, _) in v_node.costs.idx_to_reg.iter().enumerate() {
for (wj, _) in w_node.costs.idx_to_reg.iter().enumerate() {
let mut min_total = PBQP_INF;
for (ui, _) in node.costs.idx_to_reg.iter().enumerate() {
let cost_uv = if key_uv.0 == vreg {
matrix_uv.get(ui, vi)
} else {
matrix_uv.get(vi, ui)
};
let cost_uw = if key_uw.0 == vreg {
matrix_uw.get(ui, wj)
} else {
matrix_uw.get(wj, ui)
};
let total = node.costs.costs[ui] + cost_uv + cost_uw;
if total < min_total {
min_total = total;
}
}
new_matrix.set(vi, wj, min_total);
}
}
let existing_key = (v.min(w), v.max(w));
if let Some(existing) = self.edges.get(&existing_key) {
let mut merged = existing.clone();
for i in 0..merged.rows.min(new_matrix.rows) {
for j in 0..merged.cols.min(new_matrix.cols) {
merged.data[i][j] += new_matrix.data[i][j];
}
}
self.edges.insert(existing_key, merged);
} else {
self.edges.insert(existing_key, new_matrix);
if let Some(vn) = self.nodes.get_mut(&v) {
vn.neighbors.insert(w);
}
if let Some(wn) = self.nodes.get_mut(&w) {
wn.neighbors.insert(v);
}
}
self.nodes.remove(&vreg);
if let Some(vn) = self.nodes.get_mut(&v) {
vn.neighbors.remove(&vreg);
}
if let Some(wn) = self.nodes.get_mut(&w) {
wn.neighbors.remove(&vreg);
}
self.reduction_stack.push((vreg, PbqpReduction::R2, None));
self.stats.r2_reductions += 1;
}
had_candidates
}
fn heuristic_reduce(&mut self) {
let mut worst_vreg: Option<VReg> = None;
let mut worst_cost = -PBQP_INF;
for (&vreg, node) in &self.nodes {
if node.eliminated {
continue;
}
if let Some((_, min_c)) = node.costs.min_cost_reg() {
if min_c > worst_cost {
worst_cost = min_c;
worst_vreg = Some(vreg);
}
}
}
if let Some(vreg) = worst_vreg {
let node = self.nodes.remove(&vreg).unwrap();
let neighbors: Vec<VReg> = node.neighbors.iter().copied().collect();
for neighbor in &neighbors {
let key = (vreg.min(*neighbor), vreg.max(*neighbor));
self.edges.remove(&key);
if let Some(nbr) = self.nodes.get_mut(neighbor) {
nbr.neighbors.remove(&vreg);
}
}
self.reduction_stack
.push((vreg, PbqpReduction::R0, Some(node.costs)));
}
}
fn backpropagate(&mut self) {
while let Some((vreg, reduction, saved_costs)) = self.reduction_stack.pop() {
match reduction {
PbqpReduction::R0 => {
if let Some(saved) = saved_costs {
if let Some((reg, _)) = saved.min_cost_reg() {
if let Some(node) = self.nodes.get_mut(&vreg) {
node.solution = Some(reg);
self.stats.optimal_solutions += 1;
}
} else {
self.stats.infeasible_nodes += 1;
}
} else {
if !self.nodes.contains_key(&vreg) {
let mut temp = PbqpNode::new(
vreg,
RegClass::GPR,
&(0..self.k_registers as PReg).collect::<Vec<_>>(),
);
if let Some((reg, _)) = temp.costs.min_cost_reg() {
temp.solution = Some(reg);
self.stats.optimal_solutions += 1;
}
self.nodes.insert(vreg, temp);
}
}
}
PbqpReduction::R1 => {
if !self.nodes.contains_key(&vreg) {
let mut temp = PbqpNode::new(
vreg,
RegClass::GPR,
&(0..self.k_registers as PReg).collect::<Vec<_>>(),
);
if let Some((reg, _)) = temp.costs.min_cost_reg() {
temp.solution = Some(reg);
self.stats.optimal_solutions += 1;
}
self.nodes.insert(vreg, temp);
}
}
PbqpReduction::R2 => {
if !self.nodes.contains_key(&vreg) {
let mut temp = PbqpNode::new(
vreg,
RegClass::GPR,
&(0..self.k_registers as PReg).collect::<Vec<_>>(),
);
if let Some((reg, _)) = temp.costs.min_cost_reg() {
temp.solution = Some(reg);
self.stats.optimal_solutions += 1;
}
self.nodes.insert(vreg, temp);
}
}
PbqpReduction::RN => {
if let Some(node) = self.nodes.get_mut(&vreg) {
if let Some((reg, _)) = node.costs.min_cost_reg() {
node.solution = Some(reg);
self.stats.optimal_solutions += 1;
}
}
}
}
}
for node in self.nodes.values_mut() {
if node.solution.is_none() {
if let Some((reg, _)) = node.costs.min_cost_reg() {
node.solution = Some(reg);
}
}
}
}
pub fn get_assignment(&self, vreg: VReg) -> Option<PReg> {
self.nodes.get(&vreg).and_then(|n| n.solution)
}
}
#[derive(Debug, Clone)]
pub struct RegisterFile {
pub regs: Vec<RegisterInfo>,
pub occupants: HashMap<PReg, VReg>,
pub free_mask: HashMap<RegClass, u64>,
pub reserved: HashSet<PReg>,
pub aliases: HashMap<PReg, Vec<PReg>>,
pub alias_parents: HashMap<PReg, PReg>,
pub callee_saved: HashSet<PReg>,
pub caller_saved: HashSet<PReg>,
pub class_regs: HashMap<RegClass, Vec<PReg>>,
}
#[derive(Debug, Clone)]
pub struct RegisterInfo {
pub preg: PReg,
pub name: String,
pub class: RegClass,
pub width_bits: u32,
pub is_sub_reg: bool,
pub parent: Option<PReg>,
pub reserved: bool,
pub dwarf_num: u32,
}
impl RegisterInfo {
pub fn new(preg: PReg, name: &str, class: RegClass, width_bits: u32) -> Self {
RegisterInfo {
preg,
name: name.to_string(),
class,
width_bits,
is_sub_reg: false,
parent: None,
reserved: false,
dwarf_num: 0,
}
}
}
impl RegisterFile {
pub fn new_x86_64() -> Self {
let mut rf = RegisterFile {
regs: Vec::new(),
occupants: HashMap::new(),
free_mask: HashMap::new(),
reserved: HashSet::new(),
aliases: HashMap::new(),
alias_parents: HashMap::new(),
callee_saved: HashSet::new(),
caller_saved: HashSet::new(),
class_regs: HashMap::new(),
};
rf.init_x86_64_registers();
rf
}
fn init_x86_64_registers(&mut self) {
let gprs = [
(0, "RAX", 0),
(1, "RCX", 2),
(2, "RDX", 1),
(3, "RBX", 3),
(4, "RSP", 7),
(5, "RBP", 6),
(6, "RSI", 4),
(7, "RDI", 5),
(8, "R8", 8),
(9, "R9", 9),
(10, "R10", 10),
(11, "R11", 11),
(12, "R12", 12),
(13, "R13", 13),
(14, "R14", 14),
(15, "R15", 15),
];
let mut gpr_list: Vec<PReg> = Vec::new();
for &(preg, name, dwarf_num) in &gprs {
let mut info = RegisterInfo::new(preg, name, RegClass::GPR, 64);
info.dwarf_num = dwarf_num;
if preg == 4 || preg == 5 {
info.reserved = true;
self.reserved.insert(preg);
}
self.regs.push(info);
gpr_list.push(preg);
}
self.class_regs.insert(RegClass::GPR, gpr_list);
self.aliases.insert(0, vec![0]); self.aliases.insert(1, vec![1]); self.aliases.insert(2, vec![2]); self.aliases.insert(3, vec![3]); self.aliases.insert(4, vec![4]); self.aliases.insert(5, vec![5]); self.aliases.insert(6, vec![6]); self.aliases.insert(7, vec![7]);
for i in 0..16 {
self.alias_parents.insert(i as PReg, i as PReg);
}
let mut xmm_list: Vec<PReg> = Vec::new();
for i in 0..16 {
let preg = 16 + i as PReg;
let name = format!("XMM{}", i);
let mut info = RegisterInfo::new(preg, &name, RegClass::XMM, 128);
info.dwarf_num = 17 + i;
self.regs.push(info);
xmm_list.push(preg);
}
self.class_regs.insert(RegClass::XMM, xmm_list);
for &preg in &[3, 5, 12, 13, 14, 15u32] {
self.callee_saved.insert(preg);
}
for preg in 0..16u32 {
if !self.callee_saved.contains(&preg) {
self.caller_saved.insert(preg);
}
}
self.caller_saved.remove(&4);
self.callee_saved.remove(&4);
for preg in 16..32u32 {
self.caller_saved.insert(preg);
}
self.free_mask.insert(RegClass::GPR, (1u64 << 16) - 1);
self.free_mask.insert(RegClass::XMM, (1u64 << 16) - 1);
}
pub fn occupy(&mut self, preg: PReg, vreg: VReg) {
self.occupants.insert(preg, vreg);
let local_idx = self.class_local_index(preg);
if let Some(info) = self.regs.get(preg as usize) {
let mask = self.free_mask.entry(info.class).or_insert(0);
*mask &= !(1u64 << local_idx);
}
}
pub fn release(&mut self, preg: PReg) {
self.occupants.remove(&preg);
let local_idx = self.class_local_index(preg);
if let Some(info) = self.regs.get(preg as usize) {
let mask = self.free_mask.entry(info.class).or_insert(0);
*mask |= 1u64 << local_idx;
}
}
pub fn occupant(&self, preg: PReg) -> Option<VReg> {
self.occupants.get(&preg).copied()
}
pub fn is_free(&self, preg: PReg) -> bool {
!self.occupants.contains_key(&preg)
}
pub fn is_reserved(&self, preg: PReg) -> bool {
self.reserved.contains(&preg)
}
pub fn registers_of_class(&self, class: RegClass) -> Vec<PReg> {
self.class_regs
.get(&class)
.cloned()
.unwrap_or_default()
.into_iter()
.filter(|p| !self.reserved.contains(p))
.collect()
}
pub fn allocatable_count(&self, class: RegClass) -> usize {
self.registers_of_class(class).len()
}
fn class_local_index(&self, preg: PReg) -> u32 {
if preg < 16 {
preg
} else {
preg - 16
}
}
pub fn info(&self, preg: PReg) -> Option<&RegisterInfo> {
self.regs.get(preg as usize)
}
pub fn by_name(&self, name: &str) -> Option<PReg> {
self.regs.iter().find(|r| r.name == name).map(|r| r.preg)
}
pub fn is_caller_saved(&self, preg: PReg) -> bool {
self.caller_saved.contains(&preg)
}
pub fn is_callee_saved(&self, preg: PReg) -> bool {
self.callee_saved.contains(&preg)
}
pub fn parent_reg(&self, preg: PReg) -> PReg {
self.alias_parents.get(&preg).copied().unwrap_or(preg)
}
pub fn alias(&self, a: PReg, b: PReg) -> bool {
let pa = self.parent_reg(a);
let pb = self.parent_reg(b);
pa == pb
}
pub fn call_clobbered(&self) -> HashSet<PReg> {
self.caller_saved.clone()
}
pub fn call_preserved(&self) -> HashSet<PReg> {
self.callee_saved.clone()
}
}
impl Default for RegisterFile {
fn default() -> Self {
Self::new_x86_64()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SlotIndex {
pub slot: InstrIdx,
pub phase: u8,
}
impl SlotIndex {
pub fn early(slot: InstrIdx) -> Self {
SlotIndex { slot, phase: 0 }
}
pub fn late(slot: InstrIdx) -> Self {
SlotIndex { slot, phase: 1 }
}
pub fn def_point(slot: InstrIdx) -> Self {
Self::late(slot)
}
pub fn use_point(slot: InstrIdx) -> Self {
Self::early(slot)
}
pub fn is_before(&self, other: &SlotIndex) -> bool {
self < other
}
pub fn is_after(&self, other: &SlotIndex) -> bool {
self > other
}
}
impl fmt::Display for SlotIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.slot, self.phase)
}
}
#[derive(Debug, Clone)]
pub struct IndexMap<T: Eq + std::hash::Hash + Clone> {
pub map: HashMap<T, usize>,
pub inverse: Vec<T>,
}
impl<T: Eq + std::hash::Hash + Clone> IndexMap<T> {
pub fn new() -> Self {
IndexMap {
map: HashMap::new(),
inverse: Vec::new(),
}
}
pub fn index(&mut self, key: &T) -> usize {
if let Some(&idx) = self.map.get(key) {
idx
} else {
let idx = self.inverse.len();
self.map.insert(key.clone(), idx);
self.inverse.push(key.clone());
idx
}
}
pub fn get(&self, key: &T) -> Option<usize> {
self.map.get(key).copied()
}
pub fn len(&self) -> usize {
self.inverse.len()
}
pub fn is_empty(&self) -> bool {
self.inverse.is_empty()
}
}
impl<T: Eq + std::hash::Hash + Clone> Default for IndexMap<T> {
fn default() -> Self {
Self::new()
}
}
impl LivenessAnalysis {
pub fn compute_ssa_live_intervals(&mut self) {
for &vreg in &self.all_vregs {
let def_pts = self.def_sites.get(&vreg);
let use_pts = self.use_sites.get(&vreg);
if let (Some(defs), Some(uses)) = (def_pts, use_pts) {
if let (Some(&first_def), Some(&last_use)) = (defs.first(), uses.iter().max()) {
let end = last_use + 1;
if let Some(li) = self.live_intervals.get_mut(&vreg) {
for block_id in 0..self.cfg.num_blocks() as BlockId {
let bl = &self.blocks[block_id as usize];
if bl.live_in.contains(&vreg) || bl.live_out.contains(&vreg) {
let block_instrs = &self.cfg.block(block_id).instrs;
if block_instrs.is_empty() {
continue;
}
let block_start = block_instrs.first().unwrap().idx;
let block_end = block_instrs.last().unwrap().idx + 1;
li.add_segment(block_start, block_end);
}
}
}
}
}
}
}
pub fn live_set_at(&self, point: InstrIdx, index_map: &IndexMap<VReg>) -> Vec<bool> {
let mut result = vec![false; index_map.len()];
for (&vreg, li) in &self.live_intervals {
if li.live_at(point) {
if let Some(&idx) = index_map.map.get(&vreg) {
result[idx] = true;
}
}
}
result
}
pub fn max_register_pressure(&self) -> u32 {
let mut max_pressure = 0u32;
let mut all_points: BTreeSet<InstrIdx> = BTreeSet::new();
for block_id in 0..self.cfg.num_blocks() as BlockId {
for instr in &self.cfg.block(block_id).instrs {
all_points.insert(instr.idx);
}
}
for &point in &all_points {
let count = self.live_regs_at(point).len() as u32;
if count > max_pressure {
max_pressure = count;
}
}
max_pressure
}
pub fn propagate_phi_liveness(&mut self, phi_nodes: &HashMap<BlockId, Vec<(VReg, Vec<VReg>)>>) {
for (&block_id, phis) in phi_nodes {
for (_result, sources) in phis {
for (pred_idx, &source_vreg) in sources.iter().enumerate() {
if pred_idx < self.cfg.block(block_id).preds.len() {
let pred_id = self.cfg.block(block_id).preds[pred_idx];
self.blocks[pred_id as usize].live_out.insert(source_vreg);
}
}
}
}
}
}
impl InterferenceGraph {
pub fn maximum_cardinality_search(&self) -> Vec<VReg> {
let n = self.nodes.len();
if n == 0 {
return Vec::new();
}
let mut order: Vec<VReg> = Vec::with_capacity(n);
let mut weight: HashMap<VReg, u32> = HashMap::new();
let mut unnumbered: HashSet<VReg> = self
.nodes
.keys()
.filter(|v| {
self.nodes
.get(v)
.map(|n| !n.removed && n.coalesced_into.is_none())
.unwrap_or(false)
})
.copied()
.collect();
for &v in &unnumbered {
weight.insert(v, 0);
}
for _ in 0..unnumbered.len() {
let best = unnumbered
.iter()
.max_by_key(|&&v| weight.get(&v).copied().unwrap_or(0))
.copied();
if let Some(v) = best {
unnumbered.remove(&v);
order.push(v);
if let Some(neighbors) = self.adjacency.get(&v) {
for &neighbor in neighbors {
if unnumbered.contains(&neighbor) {
*weight.entry(neighbor).or_insert(0) += 1;
}
}
}
} else {
break;
}
}
order.reverse();
order
}
pub fn is_chordal(&self) -> bool {
let peo = self.maximum_cardinality_search();
self.verify_peo(&peo)
}
fn verify_peo(&self, peo: &[VReg]) -> bool {
let pos: HashMap<VReg, usize> = peo.iter().enumerate().map(|(i, &v)| (v, i)).collect();
for (i, &v) in peo.iter().enumerate() {
let later_neighbors: Vec<VReg> = self
.adjacency
.get(&v)
.map(|s| {
s.iter()
.filter(|&&n| pos.get(&n).copied().unwrap_or(0) > i)
.copied()
.collect()
})
.unwrap_or_default();
for j in 0..later_neighbors.len() {
for k in (j + 1)..later_neighbors.len() {
if !self.interfere(later_neighbors[j], later_neighbors[k]) {
return false; }
}
}
}
true
}
pub fn greedy_color_peo(&self) -> HashMap<VReg, PReg> {
let peo = self.maximum_cardinality_search();
let pos: HashMap<VReg, usize> = peo.iter().enumerate().map(|(i, &v)| (v, i)).collect();
let mut colors: HashMap<VReg, PReg> = HashMap::new();
for &v in &peo {
let mut forbidden: HashSet<PReg> = HashSet::new();
if let Some(neighbors) = self.adjacency.get(&v) {
for &neighbor in neighbors {
if pos.get(&neighbor).copied().unwrap_or(usize::MAX)
< pos.get(&v).copied().unwrap_or(0)
{
if let Some(&color) = colors.get(&neighbor) {
forbidden.insert(color);
}
}
}
}
let mut color = 0u32;
while forbidden.contains(&color) {
color += 1;
}
colors.insert(v, color);
}
colors
}
pub fn chromatic_number(&self) -> u32 {
let colors = self.greedy_color_peo();
colors.values().max().copied().map(|c| c + 1).unwrap_or(0)
}
pub fn max_clique_heuristic(&self) -> Vec<VReg> {
let nodes: Vec<VReg> = self
.nodes
.keys()
.filter(|v| {
self.nodes
.get(v)
.map(|n| !n.removed && n.coalesced_into.is_none())
.unwrap_or(false)
})
.copied()
.collect();
let mut best_clique: Vec<VReg> = Vec::new();
for &start in &nodes {
let mut clique = vec![start];
let mut candidates: Vec<VReg> = nodes
.iter()
.filter(|&&v| v != start && nodes.contains(&v))
.copied()
.collect();
candidates.sort_by_key(|&v| -(self.degree(v) as i64));
for &candidate in &candidates {
if clique.iter().all(|&c| self.interfere(c, candidate)) {
clique.push(candidate);
}
}
if clique.len() > best_clique.len() {
best_clique = clique;
}
}
best_clique
}
}
impl RegisterCoalescer {
pub fn run_iterated_coalescing(
&mut self,
_simplify_worklist: &mut Vec<VReg>,
_freeze_worklist: &mut Vec<VReg>,
_spill_worklist: &mut Vec<VReg>,
) {
self.stats.iterations = 0;
loop {
self.stats.iterations += 1;
if self.stats.iterations > 200 {
break; }
let simplified = self.simplify_stage();
let coalesced = self.coalesce_stage();
let frozen = self.freeze_stage();
let spilled = self.select_spill_stage();
if !simplified && !coalesced && !frozen && !spilled {
break; }
}
}
fn simplify_stage(&mut self) -> bool {
let mut simplified = false;
let all_vregs: Vec<VReg> = self
.ig
.nodes
.keys()
.filter(|v| {
self.ig
.nodes
.get(v)
.map(|n| !n.removed && n.coalesced_into.is_none())
.unwrap_or(false)
})
.copied()
.collect();
for vreg in all_vregs {
let degree = self.ig.degree(vreg);
if degree < self.k_registers as u32 {
let _is_copy_related = self.is_copy_related(vreg);
self.ig.remove_node(vreg);
self.node_stack.push(vreg);
simplified = true;
}
}
simplified
}
fn coalesce_stage(&mut self) -> bool {
let before = self.coalesced;
self.conservative_coalesce();
self.coalesced > before
}
fn freeze_stage(&mut self) -> bool {
let mut frozen = false;
let mut copy_related: HashSet<VReg> = HashSet::new();
for &(u, v) in &self.copy_pairs {
if self.ig.nodes.contains_key(&u) && self.ig.nodes.contains_key(&v) {
copy_related.insert(u);
copy_related.insert(v);
}
}
for vreg in ©_related {
let degree = self.ig.degree(*vreg);
if degree < self.k_registers as u32 {
self.freeze_node(*vreg);
frozen = true;
break; }
}
frozen
}
fn freeze_node(&mut self, vreg: VReg) {
self.copy_pairs.retain(|&(u, v)| u != vreg && v != vreg);
self.stats.failed += 1;
}
fn select_spill_stage(&mut self) -> bool {
let mut candidates: Vec<(VReg, f64)> = Vec::new();
for (&vreg, node) in &self.ig.nodes {
if node.removed || node.coalesced_into.is_some() {
continue;
}
let degree = self.ig.degree(vreg);
if degree >= self.k_registers as u32 {
candidates.push((vreg, node.weight));
}
}
candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
if let Some((vreg, _)) = candidates.first().copied() {
if let Some(node) = self.ig.nodes.get_mut(&vreg) {
node.removed = true;
}
self.node_stack.push(vreg);
return true;
}
false
}
fn is_copy_related(&self, vreg: VReg) -> bool {
self.copy_pairs.iter().any(|&(u, v)| u == vreg || v == vreg)
}
}
#[derive(Debug, Clone)]
pub struct RematInfo {
pub vreg: VReg,
pub recompute_cost: f64,
pub spill_reload_cost: f64,
pub is_cheaper: bool,
pub kind: RematKind,
pub required_regs: Vec<VReg>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RematKind {
Constant,
Address,
FrameIndex,
NotRematerializable,
}
impl SpillCost {
pub fn analyze_rematerialization(
&self,
_vreg: VReg,
_def_instr: Option<&MachineInstr>,
) -> RematInfo {
RematInfo {
vreg: _vreg,
recompute_cost: f64::MAX,
spill_reload_cost: 10.0, is_cheaper: false,
kind: RematKind::NotRematerializable,
required_regs: Vec::new(),
}
}
pub fn split_cost(
&self,
vreg: VReg,
split_point: InstrIdx,
liveness: &LivenessAnalysis,
) -> f64 {
let base_cost = 2.0;
let block_id = self.find_block_for_instr(split_point);
let freq = block_id.map(|b| self.block_frequency(b)).unwrap_or(1.0);
let pressure_before = liveness.live_regs_at(split_point.saturating_sub(1)).len() as f64;
let pressure_at = liveness.live_regs_at(split_point).len() as f64;
let pressure_reduction = (pressure_before - pressure_at).max(0.0);
base_cost * freq - pressure_reduction * 0.5
}
pub fn assign_spill_slots(
&self,
spilled_regs: &HashSet<VReg>,
liveness: &LivenessAnalysis,
) -> HashMap<VReg, u32> {
let mut slots: HashMap<VReg, u32> = HashMap::new();
let mut next_slot = 0u32;
let mut sorted: Vec<(VReg, f64)> =
spilled_regs.iter().map(|&v| (v, self.weight(v))).collect();
sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
let mut allocated_ranges: Vec<(InstrIdx, InstrIdx, u32)> = Vec::new();
for (vreg, _) in sorted {
let li = match liveness.live_intervals.get(&vreg) {
Some(li) => li,
None => continue,
};
let start = li.start();
let end = li.end();
let mut reused = false;
for &(s, e, slot) in &allocated_ranges {
if e <= start || end <= s {
slots.insert(vreg, slot);
reused = true;
break;
}
}
if !reused {
slots.insert(vreg, next_slot);
allocated_ranges.push((start, end, next_slot));
next_slot += 8; }
}
slots
}
}
#[derive(Debug, Clone)]
pub struct SplitKit {
pub original_vreg: VReg,
pub split_points: Vec<SplitPoint>,
pub new_vregs: Vec<VReg>,
pub split_copies: Vec<(InstrIdx, VReg, VReg)>,
pub reg_class: RegClass,
next_vreg_id: u32,
liveness: Option<LivenessAnalysis>,
}
#[derive(Debug, Clone, Copy)]
pub struct SplitPoint {
pub point: InstrIdx,
pub cost: f64,
pub avoids_spill: bool,
pub gap_size: u32,
}
impl SplitPoint {
pub fn new(point: InstrIdx, cost: f64) -> Self {
Self {
point,
cost,
avoids_spill: false,
gap_size: 0,
}
}
pub fn with_gap(point: InstrIdx, cost: f64, gap_size: u32) -> Self {
Self {
point,
cost,
avoids_spill: false,
gap_size,
}
}
pub fn avoids_spill(mut self) -> Self {
self.avoids_spill = true;
self
}
}
impl SplitKit {
pub fn new(original_vreg: VReg, reg_class: RegClass, next_vreg: u32) -> Self {
SplitKit {
original_vreg,
split_points: Vec::new(),
new_vregs: Vec::new(),
split_copies: Vec::new(),
reg_class,
next_vreg_id: next_vreg,
liveness: None,
}
}
pub fn with_liveness(mut self, liveness: LivenessAnalysis) -> Self {
self.liveness = Some(liveness);
self
}
pub fn add_split_point(&mut self, point: SplitPoint) {
self.split_points.push(point);
self.split_points.sort_by(|a, b| {
a.point
.cmp(&b.point)
.then_with(|| a.cost.partial_cmp(&b.cost).unwrap_or(Ordering::Equal))
});
}
pub fn best_split_point(&self) -> Option<SplitPoint> {
self.split_points
.iter()
.min_by(|a, b| {
a.cost
.partial_cmp(&b.cost)
.unwrap_or(Ordering::Equal)
.then_with(|| {
if a.avoids_spill && !b.avoids_spill {
Ordering::Less
} else if !a.avoids_spill && b.avoids_spill {
Ordering::Greater
} else {
b.gap_size.cmp(&a.gap_size)
}
})
})
.copied()
}
pub fn compute_split_points(
&mut self,
li: &LiveInterval,
liveness: &LivenessAnalysis,
spill_cost: &SpillCost,
) {
self.split_points.clear();
for seg in &li.segments {
for point in seg.start..seg.end {
let live_count = liveness.live_regs_at(point).len();
let cost = spill_cost.split_cost(self.original_vreg, point, liveness);
let mut gap_size = 0u32;
if point > 0 && !li.live_at(point.saturating_sub(1)) {
gap_size += 1;
}
let sp = SplitPoint::with_gap(point, cost, gap_size);
let avoids = live_count < 8; let sp = if avoids { sp.avoids_spill() } else { sp };
self.add_split_point(sp);
}
}
}
pub fn perform_split(&mut self, split_point: InstrIdx) -> VReg {
let new_vreg = self.next_vreg_id;
self.next_vreg_id += 1;
self.new_vregs.push(new_vreg);
self.split_copies
.push((split_point, self.original_vreg, new_vreg));
new_vreg
}
pub fn next_vreg(&self) -> u32 {
self.next_vreg_id
}
pub fn num_copies(&self) -> usize {
self.split_copies.len()
}
}
impl LinearScanAllocator {
pub fn allocate_with_inactive(&mut self) {
self.stats = LinearScanStats::default();
self.assignment.clear();
self.spills.clear();
self.active.clear();
self.free_regs = (0..self.k_registers as u32).collect();
self.stats.total_vregs = self.intervals.len();
self.intervals.sort_by_key(|li| li.start());
let mut inactive: Vec<LiveInterval> = Vec::new();
let mut unhandled: VecDeque<LiveInterval> = self.intervals.clone().into();
while let Some(current) = unhandled.pop_front() {
let position = current.start();
let mut new_inactive = Vec::new();
for li in inactive.drain(..) {
if li.start() <= position && li.live_at(position) {
self.active.push(li);
} else if li.start() > position {
new_inactive.push(li);
}
}
inactive = new_inactive;
self.expire_old_intervals(position);
if !self.try_allocate_with_inactive(¤t, &mut inactive) {
self.spills.insert(current.vreg);
self.stats.spilled += 1;
} else {
self.stats.assigned += 1;
}
}
}
fn try_allocate_with_inactive(
&mut self,
current: &LiveInterval,
_inactive: &mut Vec<LiveInterval>,
) -> bool {
if let Some(preg) = self.free_regs.pop() {
self.assignment.insert(current.vreg, preg);
self.active.push(current.clone());
self.active.sort_by_key(|li| li.end());
return true;
}
let last_active = match self.active.last() {
Some(li) => li,
None => return false,
};
if last_active.end() > current.end() {
let spilled_li = self.active.pop().unwrap();
self.spills.insert(spilled_li.vreg);
if let Some(&preg) = self.assignment.get(&spilled_li.vreg) {
self.assignment.remove(&spilled_li.vreg);
self.assignment.insert(current.vreg, preg);
}
self.active.push(current.clone());
self.active.sort_by_key(|li| li.end());
true
} else {
false
}
}
pub fn coalesce_during_scan(&mut self, src_vreg: VReg, dst_vreg: VReg) -> bool {
if let Some(&src_preg) = self.assignment.get(&src_vreg) {
let dst_li = self.intervals.iter().find(|li| li.vreg == dst_vreg);
if let Some(dst_li) = dst_li {
let is_free = self.assignment.iter().all(|(&vreg, &preg)| {
if preg != src_preg {
return true;
}
if vreg == src_vreg {
return true;
}
self.intervals
.iter()
.find(|li| li.vreg == vreg)
.map(|other_li| !LivenessAnalysis::intervals_interfere(dst_li, other_li))
.unwrap_or(true)
});
if is_free {
self.assignment.insert(dst_vreg, src_preg);
return true;
}
}
}
false
}
}
impl PbqpGraph {
pub fn build_from_interference_graph(
ig: &InterferenceGraph,
spill_cost: &SpillCost,
k_registers: usize,
) -> Self {
let mut graph = PbqpGraph::new(k_registers);
for (&vreg, node) in &ig.nodes {
if node.removed || node.coalesced_into.is_some() {
continue;
}
graph.add_node(vreg, node.reg_class);
let weight = spill_cost.weight(vreg);
if let Some(pbqp_node) = graph.nodes.get_mut(&vreg) {
for ® in &pbqp_node.costs.idx_to_reg.clone() {
pbqp_node.costs.set_cost(reg, -weight * 0.1);
}
}
}
for (&u, neighbors) in &ig.adjacency {
for &v in neighbors {
if u >= v {
continue; }
let u_node = match ig.nodes.get(&u) {
Some(n) => n,
None => continue,
};
let v_node = match ig.nodes.get(&v) {
Some(n) => n,
None => continue,
};
let u_choices = match graph.nodes.get(&u) {
Some(n) => n.costs.len(),
None => continue,
};
let v_choices = match graph.nodes.get(&v) {
Some(n) => n.costs.len(),
None => continue,
};
let mut matrix = PbqpMatrix::new(u_choices, v_choices, 0.0);
let min_choices = u_choices.min(v_choices);
for i in 0..min_choices {
matrix.set(i, i, PBQP_INF);
}
graph.add_edge(u, v, matrix);
let _ = v_node;
let _ = u_node;
}
}
graph
}
}
#[derive(Debug, Clone)]
pub struct SubRegLiveness {
pub lane_intervals: HashMap<VReg, [Vec<LiveSegment>; 8]>,
pub max_lanes: usize,
}
impl SubRegLiveness {
pub fn new(max_lanes: usize) -> Self {
SubRegLiveness {
lane_intervals: HashMap::new(),
max_lanes,
}
}
pub fn add_lane_segment(&mut self, vreg: VReg, lane: usize, start: InstrIdx, end: InstrIdx) {
if lane >= self.max_lanes {
return;
}
let lanes = self.lane_intervals.entry(vreg).or_insert_with(|| {
[
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
]
});
lanes[lane].push(LiveSegment { start, end });
}
pub fn sub_reg_interfere(&self, a: VReg, b: VReg) -> bool {
let lanes_a = match self.lane_intervals.get(&a) {
Some(v) => v,
None => return false,
};
let lanes_b = match self.lane_intervals.get(&b) {
Some(v) => v,
None => return false,
};
for lane in 0..self.max_lanes {
let segs_a = &lanes_a[lane];
let segs_b = &lanes_b[lane];
for sa in segs_a {
for sb in segs_b {
if sa.start < sb.end && sb.start < sa.end {
return true;
}
}
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct RegisterPressureTracker {
pub pressure_at: HashMap<InstrIdx, u32>,
pub block_pressure: Vec<f64>,
pub high_pressure_regions: Vec<(InstrIdx, InstrIdx, u32)>,
pub available_regs: u32,
}
impl RegisterPressureTracker {
pub fn new(available_regs: u32) -> Self {
RegisterPressureTracker {
pressure_at: HashMap::new(),
block_pressure: Vec::new(),
high_pressure_regions: Vec::new(),
available_regs,
}
}
pub fn compute(&mut self, liveness: &LivenessAnalysis) {
self.pressure_at.clear();
self.block_pressure = vec![0.0; liveness.cfg.num_blocks()];
self.high_pressure_regions.clear();
let mut all_points: Vec<InstrIdx> = Vec::new();
for block_id in 0..liveness.cfg.num_blocks() as BlockId {
for instr in &liveness.cfg.block(block_id).instrs {
all_points.push(instr.idx);
}
}
all_points.sort();
all_points.dedup();
for &point in &all_points {
let count = liveness.live_regs_at(point).len() as u32;
self.pressure_at.insert(point, count);
}
let mut in_high_region = false;
let mut region_start = 0u32;
let mut peak_pressure = 0u32;
for &point in &all_points {
let pressure = self.pressure_at.get(&point).copied().unwrap_or(0);
if pressure > self.available_regs {
if !in_high_region {
in_high_region = true;
region_start = point;
peak_pressure = pressure;
} else {
peak_pressure = peak_pressure.max(pressure);
}
} else if in_high_region {
self.high_pressure_regions
.push((region_start, point, peak_pressure));
in_high_region = false;
}
}
if in_high_region {
if let Some(&last_pt) = all_points.last() {
self.high_pressure_regions
.push((region_start, last_pt, peak_pressure));
}
}
}
pub fn needs_splitting(&self, start: InstrIdx, end: InstrIdx) -> bool {
for &(rs, re, _) in &self.high_pressure_regions {
if rs < end && start < re {
return true; }
}
false
}
}
#[derive(Debug, Clone)]
pub struct MultiClassAllocator {
pub assignments: HashMap<RegClass, HashMap<VReg, PReg>>,
pub spills: HashMap<RegClass, HashSet<VReg>>,
pub reg_file: RegisterFile,
pub vreg_class: HashMap<VReg, RegClass>,
}
impl MultiClassAllocator {
pub fn new(reg_file: RegisterFile) -> Self {
MultiClassAllocator {
assignments: HashMap::new(),
spills: HashMap::new(),
reg_file,
vreg_class: HashMap::new(),
}
}
pub fn classify_vregs(&mut self, _instrs: &[MachineInstr]) {
self.vreg_class.clear();
}
pub fn class_of(&self, vreg: VReg) -> RegClass {
self.vreg_class.get(&vreg).copied().unwrap_or(RegClass::GPR)
}
pub fn get_assignment(&self, vreg: VReg) -> Option<PReg> {
let class = self.class_of(vreg);
self.assignments
.get(&class)
.and_then(|map| map.get(&vreg).copied())
}
}
#[derive(Debug, Clone)]
pub struct ChaitinBriggsAllocator {
pub ig: InterferenceGraph,
pub k: usize,
pub coloring: HashMap<VReg, PReg>,
pub stack: Vec<VReg>,
pub spills: HashSet<VReg>,
pub coalesced: HashMap<VReg, VReg>,
pub stats: ColoringStats,
}
#[derive(Debug, Clone, Default)]
pub struct ColoringStats {
pub simplified: usize,
pub coalesced: usize,
pub frozen: usize,
pub spilled: usize,
pub colored: usize,
pub iterations: usize,
}
impl ChaitinBriggsAllocator {
pub fn new(ig: InterferenceGraph, k: usize) -> Self {
ChaitinBriggsAllocator {
ig,
k,
coloring: HashMap::new(),
stack: Vec::new(),
spills: HashSet::new(),
coalesced: HashMap::new(),
stats: ColoringStats::default(),
}
}
pub fn allocate(&mut self) -> &HashMap<VReg, PReg> {
self.build();
self.select();
&self.coloring
}
fn build(&mut self) {
loop {
self.stats.iterations += 1;
if self.stats.iterations > 200 {
break;
}
if self.simplify_one() {
continue;
}
if self.coalesce_one() {
continue;
}
if self.freeze_one() {
continue;
}
if self.spill_one() {
continue;
}
break;
}
}
fn simplify_one(&mut self) -> bool {
for (&vreg, node) in &self.ig.nodes {
if node.removed || node.coalesced_into.is_some() {
continue;
}
let degree = self.ig.degree(vreg);
if degree < self.k as u32 {
self.ig.remove_node(vreg);
self.stack.push(vreg);
self.stats.simplified += 1;
return true;
}
}
false
}
fn coalesce_one(&mut self) -> bool {
let nodes: Vec<VReg> = self
.ig
.nodes
.iter()
.filter(|(_, n)| !n.removed && n.coalesced_into.is_none())
.map(|(&v, _)| v)
.collect();
for &u in &nodes {
for &v in &nodes {
if u >= v {
continue;
}
if !self.ig.interfere(u, v) {
self.coalesce_pair(u, v);
return true;
}
}
}
false
}
fn coalesce_pair(&mut self, u: VReg, v: VReg) {
let neighbors: Vec<VReg> = self
.ig
.adjacency
.get(&v)
.cloned()
.unwrap_or_default()
.into_iter()
.collect();
for neighbor in neighbors {
if neighbor == u {
continue;
}
self.ig.remove_edge(v, neighbor);
if !self.ig.interfere(u, neighbor) {
self.ig.add_edge(u, neighbor);
}
}
if let Some(node) = self.ig.nodes.get_mut(&v) {
node.coalesced_into = Some(u);
}
self.coalesced.insert(v, u);
self.stats.coalesced += 1;
}
fn freeze_one(&mut self) -> bool {
for (&vreg, node) in &self.ig.nodes.clone() {
if node.removed || node.coalesced_into.is_some() {
continue;
}
let degree = self.ig.degree(vreg);
if degree < self.k as u32 {
self.stats.frozen += 1;
return true;
}
}
false
}
fn spill_one(&mut self) -> bool {
let mut candidates: Vec<(VReg, f64)> = Vec::new();
for (&vreg, node) in &self.ig.nodes {
if node.removed || node.coalesced_into.is_some() {
continue;
}
let degree = self.ig.degree(vreg);
if degree >= self.k as u32 {
candidates.push((vreg, -node.weight)); }
}
candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
if let Some((vreg, _)) = candidates.first().copied() {
self.ig.remove_node(vreg);
self.spills.insert(vreg);
self.stack.push(vreg);
self.stats.spilled += 1;
return true;
}
false
}
fn select(&mut self) {
while let Some(vreg) = self.stack.pop() {
let mut forbidden: Vec<bool> = vec![false; self.k];
if let Some(neighbors) = self.ig.adjacency.get(&vreg) {
for &neighbor in neighbors {
if let Some(&color) = self.coloring.get(&neighbor) {
if (color as usize) < self.k {
forbidden[color as usize] = true;
}
}
}
}
if let Some(&alias) = self.coalesced.get(&vreg) {
if let Some(&color) = self.coloring.get(&alias) {
if (color as usize) < self.k {
forbidden[color as usize] = true;
}
}
}
let mut color: PReg = 0;
while (color as usize) < self.k && forbidden[color as usize] {
color += 1;
}
if (color as usize) < self.k {
self.coloring.insert(vreg, color);
if !self.spills.contains(&vreg) {
self.stats.colored += 1;
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct HoleAnalyzer {
pub holes: HashMap<VReg, Vec<LiveSegment>>,
}
impl HoleAnalyzer {
pub fn new() -> Self {
HoleAnalyzer {
holes: HashMap::new(),
}
}
pub fn compute(&mut self, intervals: &HashMap<VReg, LiveInterval>) {
self.holes.clear();
for (&vreg, li) in intervals {
let mut h: Vec<LiveSegment> = Vec::new();
for w in li.segments.windows(2) {
let gap_start = w[0].end;
let gap_end = w[1].start;
if gap_start < gap_end {
h.push(LiveSegment {
start: gap_start,
end: gap_end,
});
}
}
if !h.is_empty() {
self.holes.insert(vreg, h);
}
}
}
pub fn find_hole_covering(&self, vreg: VReg, point: InstrIdx) -> bool {
if let Some(h) = self.holes.get(&vreg) {
h.iter().any(|seg| seg.start <= point && point < seg.end)
} else {
false
}
}
pub fn largest_hole(&self, vreg: VReg) -> Option<LiveSegment> {
self.holes
.get(&vreg)?
.iter()
.max_by_key(|s| s.end.saturating_sub(s.start))
.copied()
}
pub fn total_reusable_slots(&self) -> u32 {
self.holes
.values()
.flat_map(|hs| hs.iter())
.map(|s| s.end.saturating_sub(s.start))
.sum()
}
}
#[derive(Debug, Clone)]
pub struct EvictionChain {
pub chain: Vec<(VReg, VReg)>,
pub total_cost: f64,
pub max_depth: u32,
}
impl EvictionChain {
pub fn new() -> Self {
EvictionChain {
chain: Vec::new(),
total_cost: 0.0,
max_depth: 0,
}
}
pub fn add_eviction(&mut self, evicted: VReg, evicting: VReg, cost: f64) {
self.chain.push((evicted, evicting));
self.total_cost += cost;
self.max_depth = self.chain.len() as u32;
}
pub fn would_cycle(&self, evicted: VReg) -> bool {
self.chain.iter().any(|&(e, _)| e == evicted)
}
pub fn is_profitable(&self, spill_alternative_cost: f64) -> bool {
self.total_cost < spill_alternative_cost
}
pub fn len(&self) -> usize {
self.chain.len()
}
pub fn is_empty(&self) -> bool {
self.chain.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct VRegRenumberer {
pub mapping: HashMap<VReg, VReg>,
pub inverse: Vec<VReg>,
next: VReg,
}
impl VRegRenumberer {
pub fn new() -> Self {
VRegRenumberer {
mapping: HashMap::new(),
inverse: Vec::new(),
next: 0,
}
}
pub fn add(&mut self, old: VReg) -> VReg {
if let Some(&new) = self.mapping.get(&old) {
return new;
}
let new = self.next;
self.next += 1;
self.mapping.insert(old, new);
if (new as usize) >= self.inverse.len() {
self.inverse.resize(new as usize + 1, NO_REG);
}
self.inverse[new as usize] = old;
new
}
pub fn get(&self, old: VReg) -> Option<VReg> {
self.mapping.get(&old).copied()
}
pub fn count(&self) -> usize {
self.next as usize
}
}
#[derive(Debug)]
pub struct RegAllocPass {
pub target: String,
pub reg_file: RegisterFile,
pub strategy: RegAllocStrategy,
pub enable_coalescing: bool,
pub enable_splitting: bool,
pub stats: RegAllocPassStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegAllocStrategy {
LinearScan,
Greedy,
Pbqp,
ChaitinBriggs,
}
#[derive(Debug, Clone, Default)]
pub struct RegAllocPassStats {
pub total_vregs: usize,
pub assigned: usize,
pub spilled: usize,
pub coalesced: usize,
pub split: usize,
pub elapsed_us: u64,
pub num_functions: u32,
}
impl RegAllocPass {
pub fn new(target: &str, strategy: RegAllocStrategy) -> Self {
RegAllocPass {
target: target.to_string(),
reg_file: RegisterFile::new_x86_64(),
strategy,
enable_coalescing: true,
enable_splitting: true,
stats: RegAllocPassStats::default(),
}
}
pub fn run_on_function(&mut self, cfg: ControlFlowGraph) -> HashMap<VReg, PReg> {
self.stats.num_functions += 1;
let mut liveness = LivenessAnalysis::new(cfg.clone());
liveness.compute();
self.stats.total_vregs = liveness.all_vregs.len();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&liveness);
if self.enable_coalescing {
let mut coalescer =
RegisterCoalescer::new(ig.clone(), self.reg_file.allocatable_count(RegClass::GPR));
coalescer.run(CoalesceStrategy::Conservative);
self.stats.coalesced = coalescer.coalesced;
}
let mut spill_cost = SpillCost::new(cfg);
spill_cost.compute(&liveness, &ig);
match self.strategy {
RegAllocStrategy::LinearScan => {
let mut allocator = LinearScanAllocator::new(
self.reg_file.allocatable_count(RegClass::GPR),
RegClass::GPR,
);
let intervals: Vec<LiveInterval> =
liveness.live_intervals.values().cloned().collect();
allocator.set_intervals(intervals);
allocator.allocate();
self.stats.assigned = allocator.stats.assigned;
self.stats.spilled = allocator.stats.spilled;
allocator.assignment
}
RegAllocStrategy::Greedy => {
let mut allocator =
GreedyAllocator::new(liveness, ig.clone(), spill_cost, self.reg_file.clone());
allocator.assign_strategy = AssignStrategy::BestFit;
allocator.enable_splitting = self.enable_splitting;
allocator.allocate();
self.stats.assigned = allocator.stats.assigned;
self.stats.spilled = allocator.stats.spilled;
self.stats.split = allocator.stats.split;
allocator.assignment
}
RegAllocStrategy::Pbqp => {
let mut pbqp_graph = PbqpGraph::build_from_interference_graph(
&ig,
&spill_cost,
self.reg_file.allocatable_count(RegClass::GPR),
);
pbqp_graph.solve();
let mut assignments = HashMap::new();
for (&vreg, node) in &pbqp_graph.nodes {
if let Some(preg) = node.solution {
assignments.insert(vreg, preg);
self.stats.assigned += 1;
} else {
self.stats.spilled += 1;
}
}
assignments
}
RegAllocStrategy::ChaitinBriggs => {
let mut allocator = ChaitinBriggsAllocator::new(
ig.clone(),
self.reg_file.allocatable_count(RegClass::GPR),
);
allocator.allocate();
self.stats.assigned = allocator.stats.colored;
self.stats.spilled = allocator.stats.spilled;
self.stats.coalesced = allocator.stats.coalesced;
allocator.coloring
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_diamond_cfg() -> ControlFlowGraph {
let mut entry = BasicBlock::new(0);
entry.add_succ(1);
entry.add_succ(2);
entry.loop_depth = 0;
let mut mid1 = BasicBlock::new(1);
mid1.add_pred(0);
mid1.add_succ(3);
mid1.loop_depth = 0;
let mut mid2 = BasicBlock::new(2);
mid2.add_pred(0);
mid2.add_succ(3);
mid2.loop_depth = 0;
let mut exit = BasicBlock::new(3);
exit.add_pred(1);
exit.add_pred(2);
exit.loop_depth = 0;
ControlFlowGraph::build(vec![entry, mid1, mid2, exit], 0)
}
fn build_linear_cfg_with_instrs() -> (ControlFlowGraph, Vec<VReg>) {
let mut block = BasicBlock::new(0);
block.loop_depth = 0;
let instrs = vec![
MachineInstr::new(0, 1, 0).def(1024).with_mnemonic("def"),
MachineInstr::new(1, 2, 0)
.use_(1024)
.def(1025)
.with_mnemonic("add"),
MachineInstr::new(2, 3, 0)
.use_(1025)
.def(1026)
.with_mnemonic("add"),
MachineInstr::new(3, 4, 0).use_(1024).with_mnemonic("use"),
MachineInstr::new(4, 5, 0)
.use_(1026)
.with_mnemonic("use")
.terminator(),
];
block.instrs = instrs;
let vregs = vec![1024, 1025, 1026];
(ControlFlowGraph::build(vec![block], 0), vregs)
}
fn build_loop_cfg() -> ControlFlowGraph {
let mut entry = BasicBlock::new(0);
entry.add_succ(1);
entry.loop_depth = 0;
let mut header = BasicBlock::new(1);
header.add_pred(0);
header.add_pred(3);
header.add_succ(2);
header.add_succ(4);
header.loop_depth = 1;
let mut body = BasicBlock::new(2);
body.add_pred(1);
body.add_succ(3);
body.loop_depth = 1;
let mut latch = BasicBlock::new(3);
latch.add_pred(2);
latch.add_succ(1);
latch.loop_depth = 1;
let mut exit = BasicBlock::new(4);
exit.add_pred(1);
exit.loop_depth = 0;
ControlFlowGraph::build(vec![entry, header, body, latch, exit], 0)
}
#[test]
fn test_liveness_diamond() {
let mut cfg = build_diamond_cfg();
cfg.block_mut(0).instrs = vec![
MachineInstr::new(0, 1, 0).def(1024).with_mnemonic("def"),
MachineInstr::new(1, 2, 0)
.use_(1024)
.term()
.with_mnemonic("br"),
];
cfg.block_mut(1).instrs = vec![MachineInstr::new(2, 3, 1)
.use_(1024)
.term()
.with_mnemonic("br")];
cfg.block_mut(2).instrs = vec![MachineInstr::new(3, 4, 2).term().with_mnemonic("br")];
cfg.block_mut(3).instrs = vec![MachineInstr::new(4, 5, 3)
.use_(1024)
.term()
.with_mnemonic("ret")];
let mut la = LivenessAnalysis::new(cfg);
la.compute();
assert!(
la.live_in(1).contains(&1024),
"v1024 should be live-in to block 1"
);
assert!(
la.live_in(2).contains(&1024),
"v1024 should be live-in to block 2"
);
assert!(
la.live_in(3).contains(&1024),
"v1024 should be live-in to block 3"
);
assert!(
!la.live_in(0).contains(&1024),
"v1024 should NOT be live-in to entry"
);
}
#[test]
fn test_liveness_linear() {
let (cfg, vregs) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
assert!(la.is_live_at(1024, 1), "v1024 live at 1");
assert!(la.is_live_at(1024, 2), "v1024 live at 2");
assert!(la.is_live_at(1024, 3), "v1024 live at 3");
assert!(!la.is_live_at(1024, 4), "v1024 not live at 4");
assert!(la.is_live_at(1025, 1), "v1025 live at 1");
assert!(la.is_live_at(1025, 2), "v1025 live at 2");
assert!(!la.is_live_at(1025, 3), "v1025 not live at 3");
assert!(la.is_live_at(1026, 2), "v1026 live at 2");
assert!(la.is_live_at(1026, 3), "v1026 live at 3");
assert!(la.is_live_at(1026, 4), "v1026 live at 4");
let _ = vregs;
}
#[test]
fn test_interference_graph_basic() {
let mut ig = InterferenceGraph::new();
let li_a = LiveInterval::new(1024, RegClass::GPR);
let li_b = LiveInterval::new(1025, RegClass::GPR);
ig.nodes
.insert(1024, InterferenceNode::new(1024, RegClass::GPR, li_a));
ig.nodes
.insert(1025, InterferenceNode::new(1025, RegClass::GPR, li_b));
ig.adjacency.insert(1024, HashSet::new());
ig.adjacency.insert(1025, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.matrix.insert(1025, HashSet::new());
ig.add_edge(1024, 1025);
assert!(ig.interfere(1024, 1025), "Should interfere");
assert!(ig.interfere(1025, 1024), "Symmetric interference");
assert_eq!(ig.degree(1024), 1);
assert_eq!(ig.degree(1025), 1);
assert_eq!(ig.edge_count, 1);
}
#[test]
fn test_interference_graph_from_liveness() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
assert!(ig.interfere(1024, 1025));
assert!(ig.interfere(1025, 1026));
assert!(ig.interfere(1024, 1026));
}
#[test]
fn test_interference_sweep_build() {
let mut li_a = LiveInterval::new(1024, RegClass::GPR);
li_a.add_segment(0, 10);
let mut li_b = LiveInterval::new(1025, RegClass::GPR);
li_b.add_segment(5, 15);
let mut li_c = LiveInterval::new(1026, RegClass::GPR);
li_c.add_segment(12, 20);
let mut ig = InterferenceGraph::new();
ig.nodes
.insert(1024, InterferenceNode::new(1024, RegClass::GPR, li_a));
ig.nodes
.insert(1025, InterferenceNode::new(1025, RegClass::GPR, li_b));
ig.nodes
.insert(1026, InterferenceNode::new(1026, RegClass::GPR, li_c));
ig.adjacency.insert(1024, HashSet::new());
ig.adjacency.insert(1025, HashSet::new());
ig.adjacency.insert(1026, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.matrix.insert(1025, HashSet::new());
ig.matrix.insert(1026, HashSet::new());
ig.add_edge(1024, 1025);
ig.add_edge(1025, 1026);
assert!(ig.interfere(1024, 1025));
assert!(ig.interfere(1025, 1026));
assert!(!ig.interfere(1024, 1026));
assert_eq!(ig.degree(1025), 2); assert_eq!(ig.degree(1024), 1);
assert_eq!(ig.degree(1026), 1);
}
#[test]
fn test_coalescer_no_interference() {
let mut ig = InterferenceGraph::new();
let li_u = LiveInterval::new(1024, RegClass::GPR);
let li_v = LiveInterval::new(1025, RegClass::GPR);
ig.nodes.insert(
1024,
InterferenceNode::new(1024, RegClass::GPR, li_u.clone()),
);
ig.nodes.insert(
1025,
InterferenceNode::new(1025, RegClass::GPR, li_v.clone()),
);
ig.adjacency.insert(1024, HashSet::new());
ig.adjacency.insert(1025, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.matrix.insert(1025, HashSet::new());
let mut coalescer = RegisterCoalescer::new(ig, 8);
coalescer.set_copy_pairs(vec![(1024, 1025)]);
coalescer.run(CoalesceStrategy::Conservative);
assert!(coalescer.coalesced >= 1);
}
#[test]
fn test_briggs_test() {
let mut ig = InterferenceGraph::new();
for vreg in &[1000u32, 1001, 1002, 1003, 1004, 1005] {
let li = LiveInterval::new(*vreg, RegClass::GPR);
ig.nodes
.insert(*vreg, InterferenceNode::new(*vreg, RegClass::GPR, li));
ig.adjacency.insert(*vreg, HashSet::new());
ig.matrix.insert(*vreg, HashSet::new());
}
ig.add_edge(1000, 1001); ig.add_edge(1000, 1002);
ig.add_edge(1000, 1003);
ig.add_edge(1001, 1004);
ig.add_edge(1001, 1005);
let coalescer = RegisterCoalescer::new(ig, 4);
assert!(coalescer.briggs_test(1000, 1001));
}
#[test]
fn test_george_test() {
let mut ig = InterferenceGraph::new();
for vreg in &[2000u32, 2001, 2002, 2003] {
let li = LiveInterval::new(*vreg, RegClass::GPR);
ig.nodes
.insert(*vreg, InterferenceNode::new(*vreg, RegClass::GPR, li));
ig.adjacency.insert(*vreg, HashSet::new());
ig.matrix.insert(*vreg, HashSet::new());
}
ig.add_edge(2000, 2001); ig.add_edge(2000, 2002); ig.add_edge(2002, 2001);
let coalescer = RegisterCoalescer::new(ig, 8);
assert!(coalescer.george_test(2000, 2001));
}
#[test]
fn test_coalescer_aggressive() {
let mut ig = InterferenceGraph::new();
for vreg in &[3000u32, 3001] {
let li = LiveInterval::new(*vreg, RegClass::GPR);
ig.nodes
.insert(*vreg, InterferenceNode::new(*vreg, RegClass::GPR, li));
ig.adjacency.insert(*vreg, HashSet::new());
ig.matrix.insert(*vreg, HashSet::new());
}
let mut coalescer = RegisterCoalescer::new(ig, 8);
coalescer.set_copy_pairs(vec![(3000, 3001)]);
coalescer.run(CoalesceStrategy::Aggressive);
assert!(coalescer.coalesced >= 1);
}
#[test]
fn test_spill_cost_basic() {
let (cfg, vregs) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let w1024 = sc.weight(1024);
let w1025 = sc.weight(1025);
let w1026 = sc.weight(1026);
assert!(w1024 > 0.0, "v1024 used twice should have positive weight");
assert!(w1025 > 0.0, "v1025 used at least once");
assert!(w1026 > 0.0, "v1026 used at least once");
let _ = vregs;
}
#[test]
fn test_spill_cost_loop_weighting() {
let cfg = build_loop_cfg();
cfg.block_mut(1).instrs = vec![MachineInstr::new(0, 1, 1)
.use_(1024)
.with_loop_depth(1)
.with_mnemonic("cmp")];
cfg.block_mut(1).loop_depth = 1;
cfg.block_mut(4).instrs = vec![MachineInstr::new(1, 2, 4)
.use_(1025)
.with_loop_depth(0)
.with_mnemonic("ret")];
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.with_loop_multiplier(10.0);
sc.compute(&la, &ig);
let w1024 = sc.weight(1024);
let w1025 = sc.weight(1025);
assert!(
w1024 > w1025,
"Loop-live register should have higher spill weight: w1024={}, w1025={}",
w1024,
w1025
);
}
#[test]
fn test_spill_cost_normalize() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.with_normalize(true);
sc.compute(&la, &ig);
for &vreg in &[1024u32, 1025, 1026] {
let w = sc.weight(vreg);
assert!(
w >= 0.0 && w <= 1.0,
"Weight for vreg {} is {} — not in [0,1]",
vreg,
w
);
}
}
#[test]
fn test_greedy_allocator_simple() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let reg_file = RegisterFile::new_x86_64();
let mut allocator = GreedyAllocator::new(la, ig, sc, reg_file);
allocator.assign_strategy = AssignStrategy::FirstFit;
allocator.allocate();
assert!(allocator.assignment.contains_key(&1024));
assert!(allocator.assignment.contains_key(&1025));
assert!(allocator.assignment.contains_key(&1026));
let r0 = allocator.assignment[&1024];
let r1 = allocator.assignment[&1025];
let r2 = allocator.assignment[&1026];
assert_ne!(r0, r1);
assert_ne!(r0, r2);
assert_ne!(r1, r2);
}
#[test]
fn test_greedy_allocator_best_fit() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let reg_file = RegisterFile::new_x86_64();
let mut allocator = GreedyAllocator::new(la, ig, sc, reg_file);
allocator.assign_strategy = AssignStrategy::BestFit;
allocator.allocate();
assert_eq!(allocator.stats.assigned, 3);
assert_eq!(allocator.stats.spilled, 0);
}
#[test]
fn test_linear_scan_basic() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 4);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(2, 6);
let mut c = LiveInterval::new(1026, RegClass::GPR);
c.add_segment(5, 8);
let mut allocator = LinearScanAllocator::new(2, RegClass::GPR);
allocator.set_intervals(vec![a, b, c]);
allocator.allocate();
assert_eq!(allocator.stats.spilled, 1);
assert_eq!(allocator.stats.assigned, 2);
}
#[test]
fn test_linear_scan_no_spill() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 4);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(5, 8);
let mut c = LiveInterval::new(1026, RegClass::GPR);
c.add_segment(9, 12);
let mut allocator = LinearScanAllocator::new(1, RegClass::GPR);
allocator.set_intervals(vec![a, b, c]);
allocator.allocate();
assert_eq!(allocator.stats.spilled, 0);
assert_eq!(allocator.stats.assigned, 3);
}
#[test]
fn test_linear_scan_second_chance() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 4);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(2, 3);
let mut allocator = LinearScanAllocator::new(1, RegClass::GPR);
allocator.set_intervals(vec![a, b]);
allocator.allocate_with_second_chance();
assert_eq!(allocator.stats.spilled, 1);
}
#[test]
fn test_pbqp_r0_reduction() {
let mut graph = PbqpGraph::new(4);
graph.add_node(1000, RegClass::GPR);
graph.add_node(1001, RegClass::GPR);
if let Some(node) = graph.nodes.get_mut(&1000) {
node.costs = PbqpVector::new(&[0], 0.0);
}
let mut matrix = PbqpMatrix::new(1, 4, 0.0);
matrix.set(0, 0, 1.0);
matrix.set(0, 1, 2.0);
graph.add_edge(1000, 1001, matrix);
graph.solve();
assert!(graph.stats.r0_reductions >= 1);
}
#[test]
fn test_pbqp_rn_reduction() {
let mut graph = PbqpGraph::new(4);
graph.add_node(2000, RegClass::GPR);
if let Some(node) = graph.nodes.get_mut(&2000) {
node.costs.set_cost(0, 1.0);
node.costs.set_cost(1, 10.0);
node.costs.set_cost(2, 100.0);
node.costs.set_cost(3, 1000.0);
}
graph.solve();
assert!(graph.stats.optimal_solutions > 0);
}
#[test]
fn test_pbqp_r1_reduction() {
let mut graph = PbqpGraph::new(4);
graph.add_node(3000, RegClass::GPR);
graph.add_node(3001, RegClass::GPR);
let matrix = PbqpMatrix::interference_matrix(4, 4);
graph.add_edge(3000, 3001, matrix);
graph.solve();
assert!(graph.stats.r1_reductions >= 1);
assert!(graph.get_assignment(3000).is_some());
assert!(graph.get_assignment(3001).is_some());
}
#[test]
fn test_pbqp_r2_reduction() {
let mut graph = PbqpGraph::new(4);
graph.add_node(4000, RegClass::GPR); graph.add_node(4001, RegClass::GPR);
graph.add_node(4002, RegClass::GPR);
let matrix = PbqpMatrix::interference_matrix(4, 4);
graph.add_edge(4000, 4001, matrix.clone());
graph.add_edge(4000, 4002, matrix);
graph.solve();
assert!(graph.stats.r2_reductions >= 1);
}
#[test]
fn test_register_file_x86_64_layout() {
let rf = RegisterFile::new_x86_64();
let gprs = rf.registers_of_class(RegClass::GPR);
assert!(gprs.len() >= 14, "At least 14 allocatable GPRs");
let xmms = rf.registers_of_class(RegClass::XMM);
assert_eq!(xmms.len(), 16);
assert!(rf.is_reserved(4));
assert!(rf.is_reserved(5));
assert!(!rf.is_reserved(0));
assert!(rf.is_callee_saved(3));
assert!(rf.is_caller_saved(0));
assert_eq!(rf.by_name("RAX"), Some(0));
assert_eq!(rf.by_name("RSP"), Some(4));
assert_eq!(rf.by_name("XMM0"), Some(16));
}
#[test]
fn test_register_occupy_release() {
let mut rf = RegisterFile::new_x86_64();
assert!(rf.is_free(0), "RAX should be free initially");
rf.occupy(0, 1024);
assert!(!rf.is_free(0), "RAX should be occupied");
assert_eq!(rf.occupant(0), Some(1024));
rf.release(0);
assert!(rf.is_free(0), "RAX should be free after release");
assert_eq!(rf.occupant(0), None);
}
#[test]
fn test_dominators() {
let cfg = build_diamond_cfg();
assert!(cfg.dominates(0, 0));
assert!(cfg.dominates(0, 1));
assert!(cfg.dominates(0, 2));
assert!(cfg.dominates(0, 3));
assert!(!cfg.dominates(1, 2));
assert!(cfg.dominates(0, 3));
}
#[test]
fn test_loop_detection() {
let cfg = build_loop_cfg();
assert!(cfg.loop_headers.contains(&1));
assert!(!cfg.loop_headers.contains(&0));
assert_eq!(cfg.block(1).loop_depth, 1); assert_eq!(cfg.block(2).loop_depth, 1); assert_eq!(cfg.block(3).loop_depth, 1); assert_eq!(cfg.block(4).loop_depth, 0); }
#[test]
fn test_live_interval_merge() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.add_segment(0, 5);
li.add_segment(5, 10);
assert_eq!(li.segments.len(), 1);
assert_eq!(li.segments[0].start, 0);
assert_eq!(li.segments[0].end, 10);
li.add_segment(12, 15);
assert_eq!(li.segments.len(), 2);
}
#[test]
fn test_live_interval_split() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.add_segment(0, 20);
let (before, after) = li.split_at(10);
assert_eq!(before.segments.len(), 1);
assert_eq!(before.segments[0].start, 0);
assert_eq!(before.segments[0].end, 10);
assert_eq!(after.segments.len(), 1);
assert_eq!(after.segments[0].start, 10);
assert_eq!(after.segments[0].end, 20);
}
#[test]
fn test_pbqp_vector_min_cost() {
let vec = PbqpVector::new(&[0, 1, 2, 3], 100.0);
let (reg, cost) = vec.min_cost_reg().expect("Should have a min");
assert_eq!(cost, 100.0);
let mut vec2 = PbqpVector::new(&[0, 1, 2], 100.0);
vec2.set_cost(1, 10.0);
let (reg2, _) = vec2.min_cost_reg().expect("Should have a min");
assert_eq!(reg2, 1);
let _ = reg;
}
#[test]
fn test_pbqp_matrix_operations() {
let mut matrix = PbqpMatrix::new(3, 3, 0.0);
matrix.set(0, 0, 5.0);
matrix.set(1, 1, 10.0);
matrix.set(2, 2, 15.0);
assert_eq!(matrix.get(0, 0), 5.0);
assert_eq!(matrix.get(1, 1), 10.0);
let col_delta = vec![1.0, 2.0, 3.0];
matrix.subtract_cols(&col_delta);
assert_eq!(matrix.get(0, 0), 4.0); assert_eq!(matrix.get(1, 1), 8.0); assert_eq!(matrix.get(2, 2), 12.0); }
#[test]
fn test_full_pipeline() {
let mut cfg = build_loop_cfg();
cfg.block_mut(0).instrs = vec![
MachineInstr::new(0, 1, 0).def(1024).with_mnemonic("def"),
MachineInstr::new(1, 2, 0).term().with_mnemonic("br"),
];
cfg.block_mut(1).instrs = vec![
MachineInstr::new(2, 3, 1)
.use_(1024)
.def(1025)
.with_mnemonic("add"),
MachineInstr::new(3, 4, 1)
.term()
.with_loop_depth(1)
.with_mnemonic("br"),
];
cfg.block_mut(2).instrs = vec![
MachineInstr::new(4, 5, 2).use_(1025).with_mnemonic("store"),
MachineInstr::new(5, 6, 2).term().with_mnemonic("br"),
];
cfg.block_mut(3).instrs = vec![MachineInstr::new(6, 7, 3)
.use_(1025)
.term()
.with_mnemonic("br")];
cfg.block_mut(4).instrs =
vec![MachineInstr::new(7, 8, 4).use_(1024).with_mnemonic("ret")];
let mut la = LivenessAnalysis::new(cfg.clone());
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut coalescer = RegisterCoalescer::new(ig.clone(), 14);
coalescer.run(CoalesceStrategy::Conservative);
let mut sc = SpillCost::new(cfg);
sc.compute(&la, &ig);
let reg_file = RegisterFile::new_x86_64();
let mut greedy = GreedyAllocator::new(la, ig, sc, reg_file);
greedy.allocate();
assert!(greedy.assignment.contains_key(&1024));
assert!(greedy.assignment.contains_key(&1025));
assert_eq!(greedy.stats.spilled, 0);
}
#[test]
fn test_interference_node_removal() {
let mut ig = InterferenceGraph::new();
let li_a = LiveInterval::new(1024, RegClass::GPR);
let li_b = LiveInterval::new(1025, RegClass::GPR);
let li_c = LiveInterval::new(1026, RegClass::GPR);
ig.nodes
.insert(1024, InterferenceNode::new(1024, RegClass::GPR, li_a));
ig.nodes
.insert(1025, InterferenceNode::new(1025, RegClass::GPR, li_b));
ig.nodes
.insert(1026, InterferenceNode::new(1026, RegClass::GPR, li_c));
ig.adjacency.insert(1024, HashSet::new());
ig.adjacency.insert(1025, HashSet::new());
ig.adjacency.insert(1026, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.matrix.insert(1025, HashSet::new());
ig.matrix.insert(1026, HashSet::new());
ig.add_edge(1024, 1025);
ig.add_edge(1024, 1026);
ig.add_edge(1025, 1026);
assert_eq!(ig.degree(1024), 2);
ig.remove_node(1024);
assert!(ig.node(1024).unwrap().removed);
assert!(ig.interfere(1025, 1026));
assert_eq!(ig.degree(1025), 1);
}
#[test]
fn test_linear_scan_stress() {
let mut intervals = Vec::new();
for i in 0..50 {
let vreg = 2000 + i as VReg;
let mut li = LiveInterval::new(vreg, RegClass::GPR);
let start = (i * 3) as InstrIdx;
let end = start + 10;
li.add_segment(start, end);
intervals.push(li);
}
let mut allocator = LinearScanAllocator::new(8, RegClass::GPR);
allocator.set_intervals(intervals);
allocator.allocate();
assert!(
allocator.stats.spilled > 0,
"Some intervals should spill with only 8 regs"
);
assert_eq!(allocator.stats.assigned + allocator.stats.spilled, 50);
}
#[test]
fn test_machine_instr_builder() {
let instr = MachineInstr::new(42, 99, 3)
.def(1024)
.use_(1025)
.copy_from_to(1026, 1027)
.terminator()
.call()
.with_loop_depth(2)
.with_mnemonic("testop");
assert_eq!(instr.idx, 42);
assert_eq!(instr.opcode, 99);
assert_eq!(instr.block, 3);
assert!(instr.defs.contains(&1024));
assert!(instr.uses.contains(&1025));
assert_eq!(instr.copy, Some((1026, 1027)));
assert!(instr.is_terminator);
assert!(instr.is_call);
assert_eq!(instr.loop_depth, 2);
assert_eq!(instr.mnemonic, "testop");
}
#[test]
fn test_spill_cost_sorted() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let sorted = sc.sorted_by_weight();
for i in 1..sorted.len() {
assert!(
sorted[i - 1].1 >= sorted[i].1,
"Sorted by decreasing weight: {} >={}",
sorted[i - 1].1,
sorted[i].1
);
}
}
#[test]
fn test_ssa_liveness_use_def_chains() {
let mut block = BasicBlock::new(0);
block.instrs = vec![
MachineInstr::new(0, 1, 0).def(1024).with_mnemonic("def"),
MachineInstr::new(1, 2, 0)
.use_(1024)
.def(1025)
.with_mnemonic("add"),
MachineInstr::new(2, 3, 0)
.use_(1024)
.use_(1025)
.def(1026)
.with_mnemonic("add"),
];
let mut cfg = ControlFlowGraph::build(vec![block], 0);
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let defs_1024 = la.def_sites.get(&1024).unwrap();
assert_eq!(defs_1024.len(), 1);
assert_eq!(defs_1024[0], 0);
let uses_1024 = la.use_sites.get(&1024).unwrap();
assert_eq!(uses_1024.len(), 2);
assert!(uses_1024.contains(&1));
assert!(uses_1024.contains(&2));
}
#[test]
fn test_interference_non_overlapping() {
let mut cfg = build_diamond_cfg();
cfg.block_mut(0).instrs = vec![
MachineInstr::new(0, 1, 0).def(1024).with_mnemonic("def"),
MachineInstr::new(1, 2, 0)
.use_(1024)
.term()
.with_mnemonic("br"),
];
cfg.block_mut(3).instrs = vec![
MachineInstr::new(4, 3, 3).def(1025).with_mnemonic("def"),
MachineInstr::new(5, 4, 3)
.use_(1025)
.term()
.with_mnemonic("ret"),
];
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
assert!(
!ig.interfere(1024, 1025),
"Non-overlapping live ranges should not interfere"
);
}
#[test]
fn test_coalescer_identity() {
let ig = InterferenceGraph::new();
let coalescer = RegisterCoalescer::new(ig, 8);
assert!(coalescer.can_coalesce(1024, 1024));
}
#[test]
fn test_coalescer_iterated() {
let mut ig = InterferenceGraph::new();
let li_a = LiveInterval::new(1024, RegClass::GPR);
let li_b = LiveInterval::new(1025, RegClass::GPR);
ig.nodes
.insert(1024, InterferenceNode::new(1024, RegClass::GPR, li_a));
ig.nodes
.insert(1025, InterferenceNode::new(1025, RegClass::GPR, li_b));
ig.adjacency.insert(1024, HashSet::new());
ig.adjacency.insert(1025, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.matrix.insert(1025, HashSet::new());
let mut coalescer = RegisterCoalescer::new(ig, 8);
coalescer.set_copy_pairs(vec![(1024, 1025)]);
coalescer.run(CoalesceStrategy::Iterated);
assert!(coalescer.coalesced >= 1);
}
#[test]
fn test_greedy_allocator_hint_aware() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
if let Some(node) = ig.node_mut(1024) {
node.hint = Some(3); }
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let reg_file = RegisterFile::new_x86_64();
assert!(!reg_file.is_reserved(3));
let mut allocator = GreedyAllocator::new(la, ig, sc, reg_file);
allocator.assign_strategy = AssignStrategy::HintAware;
allocator.allocate();
assert_eq!(allocator.assignment.get(&1024), Some(&3));
}
#[test]
fn test_linear_scan_expire() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 3);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(4, 7);
let mut c = LiveInterval::new(1026, RegClass::GPR);
c.add_segment(8, 11);
let mut allocator = LinearScanAllocator::new(1, RegClass::GPR);
allocator.set_intervals(vec![a, b, c]);
allocator.allocate();
assert_eq!(allocator.stats.spilled, 0);
assert_eq!(allocator.stats.assigned, 3);
let ra = allocator.assignment[&1024];
let rb = allocator.assignment[&1025];
let rc = allocator.assignment[&1026];
assert_eq!(ra, rb);
assert_eq!(rb, rc);
}
#[test]
fn test_live_interval_live_at() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.add_segment(5, 10);
li.add_segment(15, 20);
assert!(!li.live_at(0));
assert!(!li.live_at(4));
assert!(li.live_at(5));
assert!(li.live_at(7));
assert!(li.live_at(9));
assert!(!li.live_at(10)); assert!(!li.live_at(13));
assert!(li.live_at(15));
assert!(li.live_at(19));
assert!(!li.live_at(20));
}
#[test]
fn test_register_file_alias() {
let rf = RegisterFile::new_x86_64();
assert!(rf.alias(0, 0));
assert!(!rf.alias(0, 1));
assert_eq!(rf.parent_reg(0), 0);
}
#[test]
fn test_cfg_rpo() {
let cfg = build_diamond_cfg();
assert_eq!(cfg.rpo[0], 0);
let mut seen = vec![false; 4];
for &b in &cfg.rpo {
assert!(!seen[b as usize], "Block {} appears twice in RPO", b);
seen[b as usize] = true;
}
assert!(seen.iter().all(|&x| x), "All blocks should be in RPO");
}
#[test]
fn test_greedy_allocator_eviction() {
let mut ig = InterferenceGraph::new();
let mut intervals = Vec::new();
for i in 0..20u32 {
let vreg = 2000 + i;
let mut li = LiveInterval::new(vreg, RegClass::GPR);
li.add_segment(0, 10);
intervals.push(li.clone());
ig.nodes
.insert(vreg, InterferenceNode::new(vreg, RegClass::GPR, li));
ig.adjacency.insert(vreg, HashSet::new());
ig.matrix.insert(vreg, HashSet::new());
}
for i in 0..20u32 {
for j in (i + 1)..20u32 {
ig.add_edge(2000 + i, 2000 + j);
}
}
let mut cfg = ControlFlowGraph::build(vec![BasicBlock::new(0)], 0);
cfg.block_mut(0).instrs =
vec![MachineInstr::new(0, 1, 0).term().with_mnemonic("ret")];
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut sc = SpillCost::new(la.cfg.clone());
for i in 0..20u32 {
sc.weights.insert(2000 + i, (20 - i) as f64);
}
let reg_file = RegisterFile::new_x86_64();
let mut allocator = GreedyAllocator::new(la.clone(), ig.clone(), sc, reg_file);
allocator.assign_strategy = AssignStrategy::FirstFit;
allocator.enable_eviction_chains = true;
allocator.allocate();
let spilled = allocator.stats.spilled;
assert!(
spilled >= 6,
"Expected at least 6 spills with 20 vregs and 14 GPRs, got {}",
spilled
);
assert_eq!(allocator.stats.assigned + spilled, 20);
}
#[test]
fn test_pbqp_full_solve() {
let mut graph = PbqpGraph::new(3);
graph.add_node(5000, RegClass::GPR);
graph.add_node(5001, RegClass::GPR);
graph.add_node(5002, RegClass::GPR);
let matrix = PbqpMatrix::interference_matrix(3, 3);
graph.add_edge(5000, 5001, matrix.clone());
graph.add_edge(5001, 5002, matrix.clone());
graph.add_edge(5000, 5002, matrix);
graph.solve();
let r0 = graph.get_assignment(5000).unwrap();
let r1 = graph.get_assignment(5001).unwrap();
let r2 = graph.get_assignment(5002).unwrap();
assert_ne!(r0, r1);
assert_ne!(r1, r2);
assert_ne!(r0, r2);
}
#[test]
fn test_pbqp_coalescing_matrix() {
let matrix = PbqpMatrix::coalescing_matrix(4, 4, 5.0);
assert!(matrix.get(0, 0) < 0.0);
assert_eq!(matrix.get(0, 0), -5.0);
assert_eq!(matrix.get(0, 1), 0.0);
assert_eq!(matrix.get(1, 0), 0.0);
}
#[test]
fn test_interference_active_nodes_sorted() {
let mut ig = InterferenceGraph::new();
let vregs = [3000u32, 3001, 3002, 3003, 3004, 3005];
for &vreg in &vregs {
let li = LiveInterval::new(vreg, RegClass::GPR);
ig.nodes
.insert(vreg, InterferenceNode::new(vreg, RegClass::GPR, li));
ig.adjacency.insert(vreg, HashSet::new());
ig.matrix.insert(vreg, HashSet::new());
}
ig.add_edge(3000, 3001);
ig.add_edge(3000, 3002);
ig.add_edge(3000, 3003);
ig.add_edge(3004, 3005);
let sorted = ig.active_nodes_sorted();
assert_eq!(sorted[0].vreg, 3000);
assert_eq!(sorted[0].degree, 3);
}
#[test]
fn test_spill_cost_computation() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut sc = SpillCost::new(la.cfg.clone());
let cost = sc.spill_cost(1024, &la);
assert!(
cost > 0.0,
"Spill cost should be positive for used register"
);
assert!(
cost < 100.0,
"Spill cost should be reasonable for trivial function"
);
}
#[test]
fn test_register_call_clobbered() {
let rf = RegisterFile::new_x86_64();
let clobbered = rf.call_clobbered();
assert!(clobbered.contains(&0)); assert!(clobbered.contains(&1)); assert!(clobbered.contains(&2)); assert!(!clobbered.contains(&3)); assert!(!clobbered.contains(&4));
let preserved = rf.call_preserved();
assert!(preserved.contains(&3)); assert!(preserved.contains(&12)); }
#[test]
fn test_live_interval_split_with_gaps() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.add_segment(0, 5);
li.add_segment(10, 20);
let (before, after) = li.split_at(7);
assert_eq!(before.segments.len(), 1);
assert_eq!(before.segments[0].start, 0);
assert_eq!(before.segments[0].end, 5);
assert_eq!(after.segments.len(), 1);
assert_eq!(after.segments[0].start, 10);
assert_eq!(after.segments[0].end, 20);
}
#[test]
fn test_slot_index_ordering() {
let early_0 = SlotIndex::early(0);
let late_0 = SlotIndex::late(0);
let early_1 = SlotIndex::early(1);
assert!(early_0 < late_0);
assert!(late_0 < early_1);
assert!(early_0 < early_1);
}
#[test]
fn test_index_map() {
let mut map = IndexMap::new();
let i0 = map.index(&1024u32);
let i1 = map.index(&1025u32);
let i0_again = map.index(&1024u32);
assert_eq!(i0, 0);
assert_eq!(i1, 1);
assert_eq!(i0_again, 0);
assert_eq!(map.len(), 2);
}
#[test]
fn test_max_register_pressure() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let pressure = la.max_register_pressure();
assert!(pressure >= 2, "Expected pressure >= 2, got {}", pressure);
}
#[test]
fn test_chordal_graph() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
assert!(ig.is_chordal(), "SSA interference graph should be chordal");
}
#[test]
fn test_greedy_peo_coloring() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let colors = ig.greedy_color_peo();
let c0 = colors.get(&1024).unwrap();
let c1 = colors.get(&1025).unwrap();
let c2 = colors.get(&1026).unwrap();
assert_ne!(c0, c1);
assert_ne!(c1, c2);
assert_ne!(c0, c2);
}
#[test]
fn test_chromatic_number() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let chi = ig.chromatic_number();
assert_eq!(chi, 3);
}
#[test]
fn test_max_clique_heuristic() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let clique = ig.max_clique_heuristic();
assert!(
clique.len() >= 2,
"Expected clique of at least 2, got {}",
clique.len()
);
}
#[test]
fn test_freeze_stage() {
let mut ig = InterferenceGraph::new();
let li_a = LiveInterval::new(1024, RegClass::GPR);
let li_b = LiveInterval::new(1025, RegClass::GPR);
ig.nodes
.insert(1024, InterferenceNode::new(1024, RegClass::GPR, li_a));
ig.nodes
.insert(1025, InterferenceNode::new(1025, RegClass::GPR, li_b));
ig.adjacency.insert(1024, HashSet::new());
ig.adjacency.insert(1025, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.matrix.insert(1025, HashSet::new());
let mut coalescer = RegisterCoalescer::new(ig, 8);
coalescer.set_copy_pairs(vec![(1024, 1025)]);
coalescer.freeze_node(1024);
assert!(!coalescer.is_copy_related(1024));
assert!(!coalescer.is_copy_related(1025));
}
#[test]
fn test_iterated_coalescing_pipeline() {
let mut ig = InterferenceGraph::new();
for vreg in &[4000u32, 4001, 4002] {
let li = LiveInterval::new(*vreg, RegClass::GPR);
ig.nodes
.insert(*vreg, InterferenceNode::new(*vreg, RegClass::GPR, li));
ig.adjacency.insert(*vreg, HashSet::new());
ig.matrix.insert(*vreg, HashSet::new());
}
ig.add_edge(4000, 4001);
ig.add_edge(4000, 4002);
let mut coalescer = RegisterCoalescer::new(ig, 3);
coalescer.set_copy_pairs(vec![(4000, 4001)]);
let mut simplify_wl = Vec::new();
let mut freeze_wl = Vec::new();
let mut spill_wl = Vec::new();
coalescer.run_iterated_coalescing(&mut simplify_wl, &mut freeze_wl, &mut spill_wl);
assert!(coalescer.stats.iterations < 200);
}
#[test]
fn test_rematerialization_analysis() {
let (cfg, _) = build_linear_cfg_with_instrs();
let sc = SpillCost::new(cfg);
let info = sc.analyze_rematerialization(1024, None);
assert_eq!(info.kind, RematKind::NotRematerializable);
assert!(!info.is_cheaper);
}
#[test]
fn test_spill_slot_assignment() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let mut spilled = HashSet::new();
spilled.insert(1025);
let slots = sc.assign_spill_slots(&spilled, &la);
assert!(slots.contains_key(&1025));
}
#[test]
fn test_split_kit_compute_points() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let li = la.live_intervals.get(&1024).cloned().unwrap();
let mut kit = SplitKit::new(1024, RegClass::GPR, 2000);
kit.compute_split_points(&li, &la, &sc);
assert!(!kit.split_points.is_empty(), "Should find split points");
assert!(kit.best_split_point().is_some());
}
#[test]
fn test_split_kit_perform() {
let mut kit = SplitKit::new(1024, RegClass::GPR, 2000);
let new_vreg = kit.perform_split(5);
assert_eq!(new_vreg, 2000);
assert_eq!(kit.next_vreg(), 2001);
assert_eq!(kit.num_copies(), 1);
assert_eq!(kit.split_copies[0], (5, 1024, 2000));
}
#[test]
fn test_linear_scan_with_inactive() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 3);
a.add_segment(10, 13);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(5, 8);
let mut allocator = LinearScanAllocator::new(1, RegClass::GPR);
allocator.set_intervals(vec![a, b]);
allocator.allocate_with_inactive();
let total = allocator.stats.assigned + allocator.stats.spilled;
assert_eq!(total, 2);
}
#[test]
fn test_coalesce_during_linear_scan() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 4);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(5, 8);
let mut allocator = LinearScanAllocator::new(2, RegClass::GPR);
allocator.set_intervals(vec![a, b.clone()]);
allocator.allocate();
let coalesced = allocator.coalesce_during_scan(1024, 1025);
assert!(coalesced);
assert_eq!(
allocator.assignment.get(&1024),
allocator.assignment.get(&1025)
);
}
#[test]
fn test_chaitin_briggs_simplification() {
let mut ig = InterferenceGraph::new();
for vreg in &[5000u32, 5001, 5002] {
let li = LiveInterval::new(*vreg, RegClass::GPR);
ig.nodes
.insert(*vreg, InterferenceNode::new(*vreg, RegClass::GPR, li));
ig.adjacency.insert(*vreg, HashSet::new());
ig.matrix.insert(*vreg, HashSet::new());
}
ig.add_edge(5000, 5001);
let mut allocator = ChaitinBriggsAllocator::new(ig, 2);
allocator.allocate();
assert_eq!(allocator.stats.spilled, 0);
assert_eq!(allocator.coloring.len(), 3);
}
#[test]
fn test_chaitin_briggs_spilling() {
let mut ig = InterferenceGraph::new();
for vreg in &[5100u32, 5101, 5102] {
let mut li = LiveInterval::new(*vreg, RegClass::GPR);
li.weight = 1.0;
ig.nodes
.insert(*vreg, InterferenceNode::new(*vreg, RegClass::GPR, li));
ig.adjacency.insert(*vreg, HashSet::new());
ig.matrix.insert(*vreg, HashSet::new());
}
ig.add_edge(5100, 5101);
ig.add_edge(5101, 5102);
ig.add_edge(5100, 5102);
let mut allocator = ChaitinBriggsAllocator::new(ig, 2);
allocator.allocate();
assert!(allocator.stats.spilled >= 1);
}
#[test]
fn test_hole_analyzer() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.add_segment(0, 5);
li.add_segment(10, 15);
let mut intervals = HashMap::new();
intervals.insert(1024u32, li);
let mut analyzer = HoleAnalyzer::new();
analyzer.compute(&intervals);
let holes = analyzer.holes.get(&1024).unwrap();
assert_eq!(holes.len(), 1);
assert_eq!(holes[0].start, 5);
assert_eq!(holes[0].end, 10);
assert!(analyzer.find_hole_covering(1024, 7));
assert!(!analyzer.find_hole_covering(1024, 2));
let largest = analyzer.largest_hole(1024).unwrap();
assert_eq!(largest.end - largest.start, 5);
}
#[test]
fn test_eviction_chain() {
let mut chain = EvictionChain::new();
chain.add_eviction(1024, 1025, 10.0);
chain.add_eviction(1025, 1026, 5.0);
assert_eq!(chain.len(), 2);
assert_eq!(chain.total_cost, 15.0);
assert!(chain.would_cycle(1024));
assert!(!chain.would_cycle(1027));
assert!(chain.is_profitable(20.0));
assert!(!chain.is_profitable(10.0));
}
#[test]
fn test_vreg_renumberer() {
let mut renumber = VRegRenumberer::new();
let n1024 = renumber.add(1024);
let n1025 = renumber.add(1025);
let n1024_again = renumber.add(1024);
assert_eq!(n1024, 0);
assert_eq!(n1025, 1);
assert_eq!(n1024_again, 0);
assert_eq!(renumber.count(), 2);
assert_eq!(renumber.get(1024), Some(0));
assert_eq!(renumber.get(1025), Some(1));
assert_eq!(renumber.get(9999), None);
}
#[test]
fn test_reg_alloc_pass_greedy() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut pass = RegAllocPass::new("x86_64", RegAllocStrategy::Greedy);
let assignment = pass.run_on_function(cfg);
assert!(assignment.contains_key(&1024));
assert!(assignment.contains_key(&1025));
assert!(assignment.contains_key(&1026));
}
#[test]
fn test_reg_alloc_pass_linear_scan() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut pass = RegAllocPass::new("x86_64", RegAllocStrategy::LinearScan);
let assignment = pass.run_on_function(cfg);
assert!(assignment.contains_key(&1024));
assert!(assignment.contains_key(&1025));
assert!(assignment.contains_key(&1026));
}
#[test]
fn test_reg_alloc_pass_pbqp() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut pass = RegAllocPass::new("x86_64", RegAllocStrategy::Pbqp);
let assignment = pass.run_on_function(cfg);
assert!(!assignment.is_empty());
}
#[test]
fn test_reg_alloc_pass_chaitin_briggs() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut pass = RegAllocPass::new("x86_64", RegAllocStrategy::ChaitinBriggs);
let assignment = pass.run_on_function(cfg);
assert!(assignment.contains_key(&1024));
assert!(assignment.contains_key(&1025));
assert!(assignment.contains_key(&1026));
}
#[test]
fn test_sub_reg_liveness() {
let mut srl = SubRegLiveness::new(8);
srl.add_lane_segment(1024, 0, 0, 5);
srl.add_lane_segment(1025, 0, 6, 10);
assert!(!srl.sub_reg_interfere(1024, 1025));
srl.add_lane_segment(1026, 0, 3, 7);
assert!(srl.sub_reg_interfere(1024, 1026));
}
#[test]
fn test_register_pressure_tracker() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut tracker = RegisterPressureTracker::new(2);
tracker.compute(&la);
let _ = tracker.high_pressure_regions;
}
#[test]
fn test_liveness_empty_function() {
let cfg = ControlFlowGraph::build(vec![BasicBlock::new(0)], 0);
let mut la = LivenessAnalysis::new(cfg);
la.compute();
assert_eq!(la.num_vregs, 0);
assert_eq!(la.max_register_pressure(), 0);
}
#[test]
fn test_live_interval_length() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.add_segment(0, 5);
li.add_segment(10, 15);
assert_eq!(li.length(), 10);
}
#[test]
fn test_register_file_allocatable_count() {
let rf = RegisterFile::new_x86_64();
assert_eq!(rf.allocatable_count(RegClass::GPR), 14);
assert_eq!(rf.allocatable_count(RegClass::XMM), 16);
}
#[test]
fn test_machine_instr_all_vregs() {
let instr = MachineInstr::new(0, 1, 0)
.def(1024)
.def(1025)
.use_(1026)
.use_(1024);
let all: Vec<VReg> = instr.all_vregs().collect();
assert!(all.contains(&1024));
assert!(all.contains(&1025));
assert!(all.contains(&1026));
}
#[test]
fn test_phi_liveness_propagation() {
let cfg = build_diamond_cfg();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut phi_nodes: HashMap<BlockId, Vec<(VReg, Vec<VReg>)>> = HashMap::new();
phi_nodes.insert(3, vec![(1030, vec![1024, 1025])]);
la.propagate_phi_liveness(&phi_nodes);
assert!(la.live_out(1).contains(&1024));
assert!(la.live_out(2).contains(&1025));
}
#[test]
fn test_mixed_register_classes() {
let mut ig = InterferenceGraph::new();
let li_gpr = LiveInterval::new(1024, RegClass::GPR);
let li_xmm = LiveInterval::new(1025, RegClass::XMM);
ig.nodes
.insert(1024, InterferenceNode::new(1024, RegClass::GPR, li_gpr));
ig.nodes
.insert(1025, InterferenceNode::new(1025, RegClass::XMM, li_xmm));
ig.adjacency.insert(1024, HashSet::new());
ig.adjacency.insert(1025, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.matrix.insert(1025, HashSet::new());
ig.add_edge(1024, 1025);
assert!(ig.interfere(1024, 1025));
let rf = RegisterFile::new_x86_64();
assert!(rf.allocatable_count(RegClass::GPR) >= 14);
assert!(rf.allocatable_count(RegClass::XMM) >= 16);
}
#[test]
fn test_stress_large_function() {
let mut ig = InterferenceGraph::new();
let mut intervals = Vec::new();
for i in 0..100u32 {
let vreg = 2000 + i;
let mut li = LiveInterval::new(vreg, RegClass::GPR);
li.add_segment(i, i + 5);
intervals.push(li.clone());
ig.nodes
.insert(vreg, InterferenceNode::new(vreg, RegClass::GPR, li));
ig.adjacency.insert(vreg, HashSet::new());
ig.matrix.insert(vreg, HashSet::new());
for j in i.saturating_sub(4)..i {
if j < i {
ig.add_edge(2000 + i, 2000 + j);
}
}
}
let mut allocator = LinearScanAllocator::new(8, RegClass::GPR);
allocator.set_intervals(intervals);
allocator.allocate();
assert_eq!(allocator.stats.assigned + allocator.stats.spilled, 100);
}
#[test]
fn test_pbqp_matrix_builder() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let pbqp_graph = PbqpGraph::build_from_interference_graph(&ig, &sc, 8);
assert!(pbqp_graph.nodes.contains_key(&1024));
assert!(pbqp_graph.nodes.contains_key(&1025));
assert!(pbqp_graph.nodes.contains_key(&1026));
}
#[test]
fn test_pressure_tracker_needs_splitting() {
let mut tracker = RegisterPressureTracker::new(4);
tracker.high_pressure_regions.push((5, 10, 6));
assert!(tracker.needs_splitting(6, 8));
assert!(!tracker.needs_splitting(0, 3));
assert!(tracker.needs_splitting(8, 12));
}
#[test]
fn test_pbqp_empty_graph() {
let mut graph = PbqpGraph::new(4);
graph.solve();
assert_eq!(graph.reduction_stack.len(), 0);
}
#[test]
fn test_pbqp_single_node() {
let mut graph = PbqpGraph::new(4);
graph.add_node(7000, RegClass::GPR);
graph.solve();
assert!(graph.get_assignment(7000).is_some());
}
#[test]
fn test_pbqp_coalescing_benefit() {
let mut graph = PbqpGraph::new(4);
graph.add_node(8000, RegClass::GPR);
graph.add_node(8001, RegClass::GPR);
let matrix = PbqpMatrix::coalescing_matrix(4, 4, 5.0);
graph.add_edge(8000, 8001, matrix);
graph.solve();
assert!(graph.get_assignment(8000).is_some());
assert!(graph.get_assignment(8001).is_some());
}
#[test]
fn test_live_interval_is_contiguous() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
assert!(li.is_contiguous()); li.add_segment(0, 5);
assert!(li.is_contiguous());
li.add_segment(5, 10);
assert!(li.is_contiguous()); li.add_segment(12, 15);
assert!(!li.is_contiguous()); }
#[test]
fn test_interference_graph_clear() {
let mut ig = InterferenceGraph::new();
let li = LiveInterval::new(1024, RegClass::GPR);
ig.nodes
.insert(1024, InterferenceNode::new(1024, RegClass::GPR, li));
ig.adjacency.insert(1024, HashSet::new());
ig.matrix.insert(1024, HashSet::new());
ig.add_edge(1024, 1025);
ig.clear();
assert_eq!(ig.nodes.len(), 0);
assert_eq!(ig.edge_count, 0);
}
#[test]
fn test_spill_cost_find_block_for_instr() {
let mut cfg = build_diamond_cfg();
cfg.block_mut(0).instrs = vec![
MachineInstr::new(0, 1, 0).with_mnemonic("nop"),
MachineInstr::new(1, 2, 0).with_mnemonic("nop"),
];
cfg.block_mut(1).instrs = vec![MachineInstr::new(2, 3, 1).with_mnemonic("nop")];
let sc = SpillCost::new(cfg);
let _ = sc;
}
#[test]
fn test_reg_class_default_counts() {
assert_eq!(RegClass::GPR.default_count(), 16);
assert_eq!(RegClass::XMM.default_count(), 16);
assert_eq!(RegClass::Mask.default_count(), 8);
assert_eq!(RegClass::Flags.default_count(), 1);
}
#[test]
fn test_reg_class_has_aliases() {
assert!(RegClass::GPR.has_aliases());
assert!(!RegClass::XMM.has_aliases());
assert!(!RegClass::Mask.has_aliases());
assert!(!RegClass::Flags.has_aliases());
}
#[test]
fn test_reg_class_display() {
assert_eq!(format!("{}", RegClass::GPR), "GPR");
assert_eq!(format!("{}", RegClass::XMM), "XMM");
assert_eq!(format!("{}", RegClass::Mask), "MASK");
assert_eq!(format!("{}", RegClass::Flags), "FLAGS");
}
#[test]
fn test_slot_index_display() {
assert_eq!(format!("{}", SlotIndex::early(42)), "42.0");
assert_eq!(format!("{}", SlotIndex::late(42)), "42.1");
}
#[test]
fn test_pbqp_vector_has_finite_cost() {
let vec = PbqpVector::new(&[0, 1], 0.0);
assert!(vec.has_finite_cost());
let vec2 = PbqpVector::new(&[0], PBQP_INF);
assert!(!vec2.has_finite_cost());
}
#[test]
fn test_live_interval_segment_at() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.add_segment(5, 10);
li.add_segment(15, 20);
assert!(li.segment_at(7).is_some());
assert!(li.segment_at(12).is_none());
assert_eq!(li.segment_at(7).unwrap().start, 5);
}
#[test]
fn test_hole_analyzer_total_reusable_slots() {
let mut li_a = LiveInterval::new(1024, RegClass::GPR);
li_a.add_segment(0, 5);
li_a.add_segment(10, 15);
let mut li_b = LiveInterval::new(1025, RegClass::GPR);
li_b.add_segment(20, 22);
li_b.add_segment(25, 35);
let mut intervals = HashMap::new();
intervals.insert(1024u32, li_a);
intervals.insert(1025u32, li_b);
let mut analyzer = HoleAnalyzer::new();
analyzer.compute(&intervals);
let reusable = analyzer.total_reusable_slots();
assert!(reusable > 0);
}
#[test]
fn test_eviction_chain_empty() {
let chain = EvictionChain::new();
assert!(chain.is_empty());
assert_eq!(chain.len(), 0);
assert_eq!(chain.total_cost, 0.0);
}
#[test]
fn test_full_allocation_pipeline_stress() {
let cfg = build_loop_cfg();
let mut pass = RegAllocPass::new("x86_64", RegAllocStrategy::Greedy);
let assignment = pass.run_on_function(cfg);
assert_eq!(pass.stats.num_functions, 1);
let _ = assignment;
}
#[test]
fn test_liveness_intervals_interfere_static() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 10);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(5, 15);
assert!(LivenessAnalysis::intervals_interfere(&a, &b));
let mut c = LiveInterval::new(1026, RegClass::GPR);
c.add_segment(10, 20);
assert!(!LivenessAnalysis::intervals_interfere(&a, &c));
}
#[test]
fn test_interference_clique_lower_bound() {
let mut ig = InterferenceGraph::new();
for vreg in &[9000u32, 9001, 9002, 9003] {
let mut li = LiveInterval::new(*vreg, RegClass::GPR);
li.weight = 1.0;
ig.nodes
.insert(*vreg, InterferenceNode::new(*vreg, RegClass::GPR, li));
ig.adjacency.insert(*vreg, HashSet::new());
ig.matrix.insert(*vreg, HashSet::new());
}
ig.add_edge(9000, 9001);
ig.add_edge(9000, 9002);
ig.add_edge(9000, 9003);
let bound = ig.clique_lower_bound();
assert!(bound >= 4);
}
#[test]
fn test_greedy_allocator_no_splitting() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let reg_file = RegisterFile::new_x86_64();
let mut allocator = GreedyAllocator::new(la, ig, sc, reg_file);
allocator.enable_splitting = false;
allocator.assign_strategy = AssignStrategy::FirstFit;
allocator.allocate();
assert_eq!(allocator.stats.assigned, 3);
}
#[test]
fn test_greedy_allocator_no_eviction_chains() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let reg_file = RegisterFile::new_x86_64();
let mut allocator = GreedyAllocator::new(la, ig, sc, reg_file);
allocator.enable_eviction_chains = false;
allocator.assign_strategy = AssignStrategy::FirstFit;
allocator.allocate();
assert_eq!(allocator.stats.assigned, 3);
}
#[test]
fn test_pbqp_vector_add_cost() {
let mut vec = PbqpVector::new(&[0, 1], 10.0);
vec.add_cost(0, 5.0);
assert_eq!(vec.get_cost(0), Some(15.0));
assert_eq!(vec.get_cost(1), Some(10.0));
}
#[test]
fn test_pbqp_vector_empty_len() {
let vec = PbqpVector::new(&[], 0.0);
assert!(vec.is_empty());
assert_eq!(vec.len(), 0);
let vec2 = PbqpVector::new(&[0, 1, 2], 0.0);
assert!(!vec2.is_empty());
assert_eq!(vec2.len(), 3);
}
#[test]
fn test_pbqp_matrix_row_col_min() {
let mut matrix = PbqpMatrix::new(3, 3, 100.0);
matrix.set(0, 1, 5.0);
matrix.set(2, 0, 10.0);
let row_min_0 = matrix.row_min(0);
assert_eq!(row_min_0, 5.0);
let col_min_0 = matrix.col_min(0);
assert_eq!(col_min_0, 10.0);
}
#[test]
fn test_live_interval_hint_and_assigned() {
let mut li = LiveInterval::new(1024, RegClass::GPR);
li.hint = Some(3);
li.assigned_reg = Some(7);
li.must_spill = true;
assert_eq!(li.hint, Some(3));
assert_eq!(li.assigned_reg, Some(7));
assert!(li.must_spill);
}
#[test]
fn test_greedy_count_future_conflicts() {
let (cfg, _) = build_linear_cfg_with_instrs();
let mut la = LivenessAnalysis::new(cfg);
la.compute();
let mut ig = InterferenceGraph::new();
ig.build_from_liveness_sweep(&la);
let mut sc = SpillCost::new(la.cfg.clone());
sc.compute(&la, &ig);
let reg_file = RegisterFile::new_x86_64();
let allocator = GreedyAllocator::new(la, ig, sc, reg_file);
let conflicts = allocator.count_future_conflicts(1024, 0);
assert!(conflicts > 0, "v1024 interferes with v1025 and v1026");
}
#[test]
fn test_linear_scan_is_spilled() {
let mut a = LiveInterval::new(1024, RegClass::GPR);
a.add_segment(0, 5);
let mut b = LiveInterval::new(1025, RegClass::GPR);
b.add_segment(2, 4);
let mut allocator = LinearScanAllocator::new(1, RegClass::GPR);
allocator.set_intervals(vec![a, b]);
allocator.allocate();
assert!(allocator.is_spilled(1024) || allocator.is_spilled(1025));
}
}