use std::collections::{HashMap, HashSet, VecDeque};
pub type PbqpCost = f64;
pub const PBQP_INF: f64 = 1e12;
#[derive(Debug, Clone)]
pub struct PbqpVector {
pub costs: Vec<f64>,
pub reg_to_idx: HashMap<u32, usize>,
pub idx_to_reg: Vec<u32>,
}
impl PbqpVector {
pub fn new(allowed_regs: &[u32], default_cost: f64) -> 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: u32) -> Option<f64> {
self.reg_to_idx.get(®).map(|&i| self.costs[i])
}
pub fn set_cost(&mut self, reg: u32, cost: f64) {
if let Some(&i) = self.reg_to_idx.get(®) {
self.costs[i] = cost;
}
}
pub fn add_cost(&mut self, reg: u32, cost: f64) {
if let Some(&i) = self.reg_to_idx.get(®) {
self.costs[i] += cost;
}
}
pub fn min_cost_reg(&self) -> Option<(u32, f64)> {
self.costs
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.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 num_choices(&self) -> usize {
self.idx_to_reg.len()
}
}
#[derive(Debug, Clone)]
pub struct PbqpMatrix {
pub matrix: Vec<Vec<f64>>,
pub rows: usize,
pub cols: usize,
}
impl PbqpMatrix {
pub fn new(rows: usize, cols: usize) -> Self {
PbqpMatrix {
matrix: vec![vec![0.0; cols]; rows],
rows,
cols,
}
}
pub fn identity_with_benefit(size: usize, benefit: f64) -> Self {
let mut matrix = vec![vec![0.0; size]; size];
for i in 0..size {
matrix[i][i] = -benefit;
}
PbqpMatrix {
matrix,
rows: size,
cols: size,
}
}
pub fn interference_matrix(size: usize) -> Self {
let mut matrix = vec![vec![0.0; size]; size];
for i in 0..size {
matrix[i][i] = PBQP_INF; }
PbqpMatrix {
matrix,
rows: size,
cols: size,
}
}
pub fn get(&self, row: usize, col: usize) -> f64 {
self.matrix[row][col]
}
pub fn set(&mut self, row: usize, col: usize, cost: f64) {
self.matrix[row][col] = cost;
}
pub fn add(&mut self, row: usize, col: usize, cost: f64) {
self.matrix[row][col] += cost;
}
pub fn col_min(&self, col: usize) -> f64 {
self.matrix
.iter()
.map(|row| row[col])
.fold(f64::MAX, f64::min)
}
pub fn row_min(&self, row: usize) -> f64 {
self.matrix[row].iter().fold(f64::MAX, |a, &b| a.min(b))
}
}
#[derive(Debug, Clone)]
pub struct PbqpNode {
pub vreg: u32,
pub costs: PbqpVector,
pub reduced: bool,
pub reduction: Option<ReductionKind>,
pub assignment: Option<u32>,
pub solution_cost: f64,
pub spilled: bool,
pub spill_offset: Option<i64>,
pub allowed_regs: HashSet<u32>,
pub restricted_regs: HashSet<u32>,
pub loop_depth: u32,
}
impl PbqpNode {
pub fn new(vreg: u32, allowed_regs: &[u32], default_cost: f64) -> Self {
PbqpNode {
vreg,
costs: PbqpVector::new(allowed_regs, default_cost),
reduced: false,
reduction: None,
assignment: None,
solution_cost: 0.0,
spilled: false,
spill_offset: None,
allowed_regs: allowed_regs.iter().copied().collect(),
restricted_regs: HashSet::new(),
loop_depth: 0,
}
}
pub fn is_allowed(&self, reg: u32) -> bool {
!self.restricted_regs.contains(®)
&& (self.allowed_regs.is_empty() || self.allowed_regs.contains(®))
}
pub fn restrict_reg(&mut self, reg: u32) {
self.restricted_regs.insert(reg);
self.costs.set_cost(reg, PBQP_INF);
}
}
#[derive(Debug, Clone)]
pub struct PbqpEdge {
pub src: u32,
pub dst: u32,
pub costs: PbqpMatrix,
pub reduced: bool,
pub coalescing_benefit: f64,
}
impl PbqpEdge {
pub fn new(src: u32, dst: u32, num_regs_src: usize, num_regs_dst: usize) -> Self {
PbqpEdge {
src,
dst,
costs: PbqpMatrix::interference_matrix(num_regs_src.max(num_regs_dst)),
reduced: false,
coalescing_benefit: 0.0,
}
}
pub fn with_coalescing_benefit(src: u32, dst: u32, num_regs: usize, benefit: f64) -> Self {
PbqpEdge {
src,
dst,
costs: PbqpMatrix::identity_with_benefit(num_regs, benefit),
reduced: false,
coalescing_benefit: benefit,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReductionKind {
R0,
R1,
R2,
RN,
RM,
}
#[derive(Debug, Clone)]
pub struct PbqpSolution {
pub vreg: u32,
pub phys_reg: Option<u32>,
pub spilled: bool,
pub spill_offset: Option<i64>,
pub cost: f64,
}
#[derive(Debug, Clone)]
pub struct PbqpGraph {
pub nodes: HashMap<u32, PbqpNode>,
pub edges: Vec<PbqpEdge>,
pub adjacency: HashMap<u32, Vec<usize>>,
pub reduction_stack: Vec<u32>,
pub reduction_data: Vec<ReductionData>,
}
#[derive(Debug, Clone)]
pub enum ReductionData {
R0Data { vreg: u32, reg: u32, cost: f64 },
R1Data {
vreg: u32,
neighbor: u32,
optimized_costs: PbqpVector,
},
R2Data {
vreg: u32,
neighbor_a: u32,
neighbor_b: u32,
reduced_matrix: PbqpMatrix,
},
SpillData { vreg: u32, offset: i64 },
}
impl PbqpGraph {
pub fn new() -> Self {
PbqpGraph {
nodes: HashMap::new(),
edges: Vec::new(),
adjacency: HashMap::new(),
reduction_stack: Vec::new(),
reduction_data: Vec::new(),
}
}
pub fn add_node(&mut self, node: PbqpNode) {
self.adjacency.entry(node.vreg).or_default();
self.nodes.insert(node.vreg, node);
}
pub fn add_edge(&mut self, edge: PbqpEdge) {
let idx = self.edges.len();
self.adjacency.entry(edge.src).or_default().push(idx);
self.adjacency.entry(edge.dst).or_default().push(idx);
self.edges.push(edge);
}
pub fn degree(&self, vreg: u32) -> usize {
self.adjacency.get(&vreg).map(|v| v.len()).unwrap_or(0)
}
pub fn neighbors(&self, vreg: u32) -> Vec<u32> {
let mut result = Vec::new();
if let Some(edge_indices) = self.adjacency.get(&vreg) {
for &idx in edge_indices {
let edge = &self.edges[idx];
if edge.src == vreg {
result.push(edge.dst);
} else {
result.push(edge.src);
}
}
}
result
}
pub fn remove_node(&mut self, vreg: u32) {
if let Some(edge_indices) = self.adjacency.remove(&vreg) {
for &edge_idx in &edge_indices {
let edge = &self.edges[edge_idx];
let other = if edge.src == vreg { edge.dst } else { edge.src };
if let Some(other_edges) = self.adjacency.get_mut(&other) {
other_edges.retain(|&e| e != edge_idx);
}
}
}
self.nodes.remove(&vreg);
}
}
impl Default for PbqpGraph {
fn default() -> Self {
PbqpGraph::new()
}
}
#[derive(Debug, Clone)]
pub struct PbqpSolver {
pub graph: PbqpGraph,
pub solution: HashMap<u32, PbqpSolution>,
pub total_cost: f64,
pub spill_count: usize,
pub max_iterations: usize,
}
impl PbqpSolver {
pub fn new() -> Self {
PbqpSolver {
graph: PbqpGraph::new(),
solution: HashMap::new(),
total_cost: 0.0,
spill_count: 0,
max_iterations: 100,
}
}
pub fn init_graph(
&mut self,
vregs: &[u32],
allowed_regs: &[u32],
interferences: &[(u32, u32)],
coalescing_pairs: &[(u32, u32)],
) {
for &vreg in vregs {
let node = PbqpNode::new(vreg, allowed_regs, 0.0);
self.graph.add_node(node);
}
for &(src, dst) in interferences {
let num_regs = allowed_regs.len();
let edge = PbqpEdge::new(src, dst, num_regs, num_regs);
self.graph.add_edge(edge);
}
for &(src, dst) in coalescing_pairs {
let num_regs = allowed_regs.len();
let edge = PbqpEdge::with_coalescing_benefit(src, dst, num_regs, 5.0);
self.graph.add_edge(edge);
}
}
pub fn solve(&mut self) -> HashMap<u32, PbqpSolution> {
self.reduce_graph();
self.backpropagate();
self.solution.clone()
}
fn reduce_graph(&mut self) {
let mut iteration = 0;
loop {
if iteration >= self.max_iterations {
break;
}
iteration += 1;
let mut reduced_any = false;
reduced_any |= self.apply_r0();
reduced_any |= self.apply_r1();
reduced_any |= self.apply_r2();
if !reduced_any {
if let Some(vreg) = self.select_spill_candidate() {
self.spill_node(vreg);
reduced_any = true;
} else {
break; }
}
if self.graph.nodes.is_empty() {
break;
}
}
}
fn apply_r0(&mut self) -> bool {
let mut reduced = false;
let independent: Vec<u32> = self
.graph
.nodes
.keys()
.filter(|&&vreg| self.graph.degree(vreg) == 0)
.copied()
.collect();
for vreg in independent {
if let Some(node) = self.graph.nodes.get(&vreg) {
if let Some((reg, cost)) = node.costs.min_cost_reg() {
if cost < PBQP_INF / 2.0 {
self.graph
.reduction_data
.push(ReductionData::R0Data { vreg, reg, cost });
self.graph.reduction_stack.push(vreg);
self.graph.remove_node(vreg);
reduced = true;
}
}
}
}
reduced
}
fn apply_r1(&mut self) -> bool {
let mut reduced = false;
let degree1: Vec<u32> = self
.graph
.nodes
.keys()
.filter(|&&vreg| self.graph.degree(vreg) == 1)
.copied()
.collect();
for vreg in degree1 {
let neighbors = self.graph.neighbors(vreg);
if neighbors.is_empty() {
continue;
}
let neighbor = neighbors[0];
let edge_idx = self.graph.adjacency[&vreg][0];
let edge = &self.graph.edges[edge_idx];
let node_costs = self.graph.nodes.get(&vreg).map(|n| n.costs.clone());
if let (Some(node_costs), Some(neighbor_node)) =
(node_costs, self.graph.nodes.get_mut(&neighbor))
{
let mut new_costs = neighbor_node.costs.costs.clone();
for (neighbor_idx, _) in neighbor_node.costs.idx_to_reg.iter().enumerate() {
let mut min_contrib = f64::MAX;
for (node_idx, _) in node_costs.idx_to_reg.iter().enumerate() {
let contrib = node_costs.costs[node_idx]
+ if edge.src == vreg {
edge.costs.matrix[node_idx][neighbor_idx]
} else {
edge.costs.matrix[neighbor_idx][node_idx]
};
min_contrib = min_contrib.min(contrib);
}
new_costs[neighbor_idx] += min_contrib;
}
neighbor_node.costs.costs = new_costs;
self.graph.reduction_data.push(ReductionData::R1Data {
vreg,
neighbor,
optimized_costs: node_costs.clone(),
});
}
self.graph.reduction_stack.push(vreg);
self.graph.remove_node(vreg);
reduced = true;
}
reduced
}
fn apply_r2(&mut self) -> bool {
let mut reduced = false;
let degree2: Vec<u32> = self
.graph
.nodes
.keys()
.filter(|&&vreg| self.graph.degree(vreg) == 2)
.copied()
.collect();
for vreg in degree2 {
let neighbors = self.graph.neighbors(vreg);
if neighbors.len() < 2 {
continue;
}
let na = neighbors[0];
let nb = neighbors[1];
let mut edge_a_idx: Option<usize> = None;
let mut edge_b_idx: Option<usize> = None;
for &e_idx in &self.graph.adjacency[&vreg] {
let edge = &self.graph.edges[e_idx];
if edge.src == na || edge.dst == na {
edge_a_idx = Some(e_idx);
}
if edge.src == nb || edge.dst == nb {
edge_b_idx = Some(e_idx);
}
}
if let (Some(ea_idx), Some(eb_idx)) = (edge_a_idx, edge_b_idx) {
let edge_a = &self.graph.edges[ea_idx];
let edge_b = &self.graph.edges[eb_idx];
let node = &self.graph.nodes[&vreg];
let num_a = self.graph.nodes[&na].costs.num_choices();
let num_b = self.graph.nodes[&nb].costs.num_choices();
let num_v = node.costs.num_choices();
let mut reduced_matrix = PbqpMatrix::new(num_a, num_b);
for i in 0..num_a {
for j in 0..num_b {
let mut min_val = f64::MAX;
for r in 0..num_v {
let ea_cost = if edge_a.src == na {
edge_a.costs.matrix[i][r]
} else {
edge_a.costs.matrix[r][i]
};
let eb_cost = if edge_b.src == nb {
edge_b.costs.matrix[j][r]
} else {
edge_b.costs.matrix[r][j]
};
let val = node.costs.costs[r] + ea_cost + eb_cost;
min_val = min_val.min(val);
}
reduced_matrix.matrix[i][j] = min_val;
}
}
self.graph.reduction_data.push(ReductionData::R2Data {
vreg,
neighbor_a: na,
neighbor_b: nb,
reduced_matrix: reduced_matrix.clone(),
});
let new_edge = PbqpEdge {
src: na,
dst: nb,
costs: reduced_matrix,
reduced: false,
coalescing_benefit: 0.0,
};
self.graph.add_edge(new_edge);
}
self.graph.reduction_stack.push(vreg);
self.graph.remove_node(vreg);
reduced = true;
}
reduced
}
fn select_spill_candidate(&self) -> Option<u32> {
self.graph
.nodes
.iter()
.filter(|(_, node)| !node.reduced)
.min_by(|(_, a), (_, b)| {
let deg_a = self.graph.degree(a.vreg).max(1) as f64;
let deg_b = self.graph.degree(b.vreg).max(1) as f64;
let cost_a = a.costs.min_cost_reg().map(|(_, c)| c).unwrap_or(PBQP_INF) / deg_a;
let cost_b = b.costs.min_cost_reg().map(|(_, c)| c).unwrap_or(PBQP_INF) / deg_b;
cost_a
.partial_cmp(&cost_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(&vreg, _)| vreg)
}
fn spill_node(&mut self, vreg: u32) {
let spill_offset = -(self.spill_count as i64 + 1) * 8;
self.graph.reduction_data.push(ReductionData::SpillData {
vreg,
offset: spill_offset,
});
self.graph.reduction_stack.push(vreg);
self.graph.remove_node(vreg);
self.spill_count += 1;
}
fn backpropagate(&mut self) {
self.solution.clear();
while let Some(vreg) = self.graph.reduction_stack.pop() {
if let Some(data) = self.graph.reduction_data.pop() {
match data {
ReductionData::R0Data { vreg, reg, cost } => {
self.solution.insert(
vreg,
PbqpSolution {
vreg,
phys_reg: Some(reg),
spilled: false,
spill_offset: None,
cost,
},
);
}
ReductionData::R1Data {
vreg,
neighbor,
optimized_costs: _,
} => {
if let Some(neighbor_sol) = self.solution.get(&neighbor) {
if let Some(neighbor_reg) = neighbor_sol.phys_reg {
let node = &self.graph.nodes[&vreg];
if let Some((best_reg, best_cost)) = node.costs.min_cost_reg() {
if best_cost < PBQP_INF / 2.0 {
self.solution.insert(
vreg,
PbqpSolution {
vreg,
phys_reg: Some(best_reg),
spilled: false,
spill_offset: None,
cost: best_cost,
},
);
let _ = neighbor_reg;
continue;
}
}
}
}
if let Some(node) = self.graph.nodes.get(&vreg) {
if let Some((reg, cost)) = node.costs.min_cost_reg() {
if cost < PBQP_INF / 2.0 {
self.solution.insert(
vreg,
PbqpSolution {
vreg,
phys_reg: Some(reg),
spilled: false,
spill_offset: None,
cost,
},
);
continue;
}
}
}
let offset = -(self.solution.len() as i64 + 1) * 8;
self.solution.insert(
vreg,
PbqpSolution {
vreg,
phys_reg: None,
spilled: true,
spill_offset: Some(offset),
cost: PBQP_INF,
},
);
}
ReductionData::R2Data {
vreg,
neighbor_a: _,
neighbor_b: _,
reduced_matrix: _,
} => {
if let Some(node) = self.graph.nodes.get(&vreg) {
if let Some((reg, cost)) = node.costs.min_cost_reg() {
if cost < PBQP_INF / 2.0 {
self.solution.insert(
vreg,
PbqpSolution {
vreg,
phys_reg: Some(reg),
spilled: false,
spill_offset: None,
cost,
},
);
continue;
}
}
}
let offset = -(self.solution.len() as i64 + 1) * 8;
self.solution.insert(
vreg,
PbqpSolution {
vreg,
phys_reg: None,
spilled: true,
spill_offset: Some(offset),
cost: PBQP_INF,
},
);
}
ReductionData::SpillData { vreg, offset } => {
self.solution.insert(
vreg,
PbqpSolution {
vreg,
phys_reg: None,
spilled: true,
spill_offset: Some(offset),
cost: PBQP_INF,
},
);
let _ = offset;
}
}
}
}
self.total_cost = self
.solution
.values()
.map(|s| s.cost)
.fold(0.0, |a, b| a + b);
}
}
impl Default for PbqpSolver {
fn default() -> Self {
PbqpSolver::new()
}
}
pub struct PbqpRegAlloc {
pub solver: PbqpSolver,
pub mf_name: String,
pub assignments: HashMap<u32, u32>,
pub spilled: Vec<u32>,
pub spill_slots: HashMap<u32, i64>,
pub available_regs: Vec<u32>,
}
impl PbqpRegAlloc {
pub fn new(available_regs: Vec<u32>) -> Self {
PbqpRegAlloc {
solver: PbqpSolver::new(),
mf_name: String::new(),
assignments: HashMap::new(),
spilled: Vec::new(),
spill_slots: HashMap::new(),
available_regs,
}
}
pub fn allocate(&mut self, func_name: &str, vreg_count: u32) {
self.mf_name = func_name.to_string();
let vregs: Vec<u32> = (0..vreg_count).collect();
let mut interferences: Vec<(u32, u32)> = Vec::new();
for i in 0..vreg_count.saturating_sub(1) {
let j = i + 1;
if i % 3 != j % 3 {
interferences.push((i, j));
}
}
self.solver
.init_graph(&vregs, &self.available_regs, &interferences, &[]);
let solution = self.solver.solve();
for (&vreg, sol) in &solution {
if sol.spilled {
self.spilled.push(vreg);
if let Some(offset) = sol.spill_offset {
self.spill_slots.insert(vreg, offset);
}
} else if let Some(phys) = sol.phys_reg {
self.assignments.insert(vreg, phys);
}
}
}
pub fn get_assignment(&self, vreg: u32) -> Option<u32> {
self.assignments.get(&vreg).copied()
}
pub fn is_spilled(&self, vreg: u32) -> bool {
self.spilled.contains(&vreg)
}
pub fn get_spill_offset(&self, vreg: u32) -> Option<i64> {
self.spill_slots.get(&vreg).copied()
}
}
impl Default for PbqpRegAlloc {
fn default() -> Self {
PbqpRegAlloc::new(vec![0, 1, 2, 3, 6, 7, 8, 9, 10, 11])
}
}
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InstrPoint {
pub block: u32,
pub instr: u32,
pub slot: u8,
}
impl InstrPoint {
pub fn new(block: u32, instr: u32, slot: u8) -> Self {
Self { block, instr, slot }
}
pub fn def(block: u32, instr: u32) -> Self {
Self {
block,
instr,
slot: 0,
}
}
pub fn r#use(block: u32, instr: u32) -> Self {
Self {
block,
instr,
slot: 1,
}
}
pub fn before(&self) -> Self {
if self.slot > 0 {
Self {
slot: self.slot - 1,
..*self
}
} else if self.instr > 0 {
Self {
instr: self.instr - 1,
slot: 1,
..*self
}
} else {
*self
}
}
pub fn after(&self) -> Self {
Self {
slot: self.slot + 1,
..*self
}
}
pub fn is_def(&self) -> bool {
self.slot == 0
}
pub fn is_use(&self) -> bool {
self.slot == 1
}
}
#[derive(Debug, Clone)]
pub struct LinearLiveInterval {
pub vreg: u32,
pub reg_class: RegClassKind,
pub segments: Vec<LinearLiveSegment>,
pub use_points: Vec<InstrPoint>,
pub def_points: Vec<InstrPoint>,
pub assigned_reg: Option<u32>,
pub spilled: bool,
pub spill_offset: i32,
pub reg_hint: Option<u32>,
pub is_fixed: bool,
pub fixed_reg: Option<u32>,
pub spill_weight: f64,
pub rematerializable: bool,
}
impl LinearLiveInterval {
pub fn new(vreg: u32, reg_class: RegClassKind) -> Self {
Self {
vreg,
reg_class,
segments: Vec::new(),
use_points: Vec::new(),
def_points: Vec::new(),
assigned_reg: None,
spilled: false,
spill_offset: -1,
reg_hint: None,
is_fixed: false,
fixed_reg: None,
spill_weight: 0.0,
rematerializable: false,
}
}
pub fn from_fixed(vreg: u32, phys_reg: u32, reg_class: RegClassKind) -> Self {
Self {
vreg,
reg_class,
segments: Vec::new(),
use_points: Vec::new(),
def_points: Vec::new(),
assigned_reg: Some(phys_reg),
spilled: false,
spill_offset: -1,
reg_hint: Some(phys_reg),
is_fixed: true,
fixed_reg: Some(phys_reg),
spill_weight: f64::MAX,
rematerializable: false,
}
}
pub fn add_segment(&mut self, start: InstrPoint, end: InstrPoint) {
self.segments.push(LinearLiveSegment { start, end });
}
pub fn add_use(&mut self, point: InstrPoint) {
self.use_points.push(point);
}
pub fn add_def(&mut self, point: InstrPoint) {
self.def_points.push(point);
}
pub fn start_point(&self) -> Option<InstrPoint> {
self.segments.iter().map(|s| s.start).min()
}
pub fn end_point(&self) -> Option<InstrPoint> {
self.segments.iter().map(|s| s.end).max()
}
pub fn live_at(&self, point: InstrPoint) -> bool {
self.segments
.iter()
.any(|s| s.start <= point && point <= s.end)
}
pub fn overlaps_with(&self, other: &LinearLiveInterval) -> bool {
for s in &self.segments {
for o in &other.segments {
if s.start <= o.end && o.start <= s.end {
return true;
}
}
}
false
}
pub fn next_use_after(&self, point: InstrPoint) -> Option<InstrPoint> {
self.use_points
.iter()
.filter(|&&p| p >= point)
.min()
.copied()
}
pub fn furthest_use_from(&self, point: InstrPoint) -> Option<InstrPoint> {
self.use_points
.iter()
.filter(|&&p| p >= point)
.max()
.copied()
}
pub fn expired_at(&self, point: InstrPoint) -> bool {
self.end_point().map(|e| e < point).unwrap_or(true)
}
pub fn sort_and_merge(&mut self) {
if self.segments.is_empty() {
return;
}
self.segments.sort_by_key(|s| s.start);
let mut merged = Vec::new();
let mut current = self.segments[0];
for seg in &self.segments[1..] {
if seg.start <= current.end {
current.end = current.end.max(seg.end);
} else {
merged.push(current);
current = *seg;
}
}
merged.push(current);
self.segments = merged;
}
pub fn set_hint(&mut self, reg: u32) {
self.reg_hint = Some(reg);
}
pub fn compute_spill_weight(&mut self, loop_depth: u32) {
let use_count = self.use_points.len() as f64;
let def_count = self.def_points.len() as f64;
let depth_factor = 10.0_f64.powi(loop_depth as i32);
let length = self
.end_point()
.and_then(|e| {
self.start_point()
.map(|s| (e.block as f64 - s.block as f64).max(1.0))
})
.unwrap_or(1.0);
self.spill_weight = (use_count + def_count) * depth_factor / length;
}
}
#[derive(Debug, Clone, Copy)]
pub struct LinearLiveSegment {
pub start: InstrPoint,
pub end: InstrPoint,
}
impl LinearLiveSegment {
pub fn new(start: InstrPoint, end: InstrPoint) -> Self {
Self { start, end }
}
pub fn contains(&self, point: InstrPoint) -> bool {
self.start <= point && point <= self.end
}
pub fn length(&self) -> u32 {
(self.end.block - self.start.block) * 1000 + (self.end.instr - self.start.instr)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegClassKind {
GPR,
FPR32,
FPR64,
VecReg,
}
impl RegClassKind {
pub fn phys_reg_range(&self) -> std::ops::Range<u32> {
match self {
RegClassKind::GPR => 0..32,
RegClassKind::FPR32 | RegClassKind::FPR64 => 32..64,
RegClassKind::VecReg => 64..96,
}
}
pub fn name(&self) -> &'static str {
match self {
RegClassKind::GPR => "GPR",
RegClassKind::FPR32 => "FPR32",
RegClassKind::FPR64 => "FPR64",
RegClassKind::VecReg => "VR",
}
}
}
#[derive(Debug, Clone)]
struct ActiveEntry {
interval: LinearLiveInterval,
phys_reg: u32,
end_point: InstrPoint,
}
impl ActiveEntry {
fn new(interval: LinearLiveInterval, phys_reg: u32, end_point: InstrPoint) -> Self {
Self {
interval,
phys_reg,
end_point,
}
}
}
pub struct LinearScanAllocator {
pub available_regs: HashMap<RegClassKind, Vec<u32>>,
pub reserved_regs: HashSet<u32>,
pub fixed_regs: HashMap<u32, u32>,
pub intervals: HashMap<u32, LinearLiveInterval>,
pub assignments: HashMap<u32, Option<u32>>,
pub spill_slots: HashMap<u32, i32>,
pub next_spill_slot: i32,
pub coalesced_count: u32,
pub spill_count: u32,
pub allocation_count: u32,
pub stats: LinearScanStats,
}
#[derive(Debug, Clone, Default)]
pub struct LinearScanStats {
pub total_intervals: usize,
pub registers_assigned: usize,
pub registers_spilled: usize,
pub copies_coalesced: usize,
pub two_address_resolved: usize,
pub load_before_use_inserted: usize,
pub store_after_def_inserted: usize,
}
impl LinearScanAllocator {
pub fn new() -> Self {
let mut available_regs = HashMap::new();
available_regs.insert(
RegClassKind::GPR,
(5..8).chain(10..18).chain(28..32).collect(),
);
available_regs.insert(
RegClassKind::FPR32,
(0..8).chain(10..18).chain(28..32).collect(),
);
available_regs.insert(
RegClassKind::FPR64,
(0..8).chain(10..18).chain(28..32).collect(),
);
available_regs.insert(RegClassKind::VecReg, (0..32).collect());
let mut reserved_regs = HashSet::new();
reserved_regs.extend(&[0, 1, 2, 3, 4, 8]);
Self {
available_regs,
reserved_regs,
fixed_regs: HashMap::new(),
intervals: HashMap::new(),
assignments: HashMap::new(),
spill_slots: HashMap::new(),
next_spill_slot: 0,
coalesced_count: 0,
spill_count: 0,
allocation_count: 0,
stats: LinearScanStats::default(),
}
}
pub fn set_available_regs(&mut self, class: RegClassKind, regs: Vec<u32>) {
self.available_regs.insert(class, regs);
}
pub fn reserve_reg(&mut self, reg: u32) {
self.reserved_regs.insert(reg);
}
pub fn add_fixed_reg(&mut self, vreg: u32, phys_reg: u32, reg_class: RegClassKind) {
self.fixed_regs.insert(vreg, phys_reg);
let interval = LinearLiveInterval::from_fixed(vreg, phys_reg, reg_class);
self.intervals.insert(vreg, interval);
}
pub fn build_interval(
&mut self,
vreg: u32,
reg_class: RegClassKind,
) -> &mut LinearLiveInterval {
self.intervals
.entry(vreg)
.or_insert_with(|| LinearLiveInterval::new(vreg, reg_class))
}
pub fn record_def(&mut self, vreg: u32, point: InstrPoint) {
if let Some(interval) = self.intervals.get_mut(&vreg) {
interval.add_def(point);
if interval.segments.is_empty() {
interval.add_segment(point, point);
} else {
let last = interval.segments.last_mut().unwrap();
if point > last.end {
last.end = point;
}
}
}
}
pub fn record_use(&mut self, vreg: u32, point: InstrPoint) {
if let Some(interval) = self.intervals.get_mut(&vreg) {
interval.add_use(point);
if let Some(last) = interval.segments.last_mut() {
if point > last.end {
last.end = point;
}
}
}
}
pub fn compute_intervals(
&mut self,
defs: &HashMap<u32, Vec<InstrPoint>>,
uses: &HashMap<u32, Vec<InstrPoint>>,
) {
for (vreg, def_points) in defs {
let class = self.infer_reg_class(*vreg);
let interval = self.build_interval(*vreg, class);
for &def in def_points {
interval.add_def(def);
}
}
for (vreg, use_points) in uses {
if let Some(interval) = self.intervals.get_mut(vreg) {
for &u in use_points {
interval.add_use(u);
}
}
}
for interval in self.intervals.values_mut() {
if interval.def_points.is_empty() {
continue;
}
interval.segments.clear();
let defs_copy: Vec<InstrPoint> = interval.def_points.clone();
for &def in &defs_copy {
let furthest_use = interval
.use_points
.iter()
.filter(|&&u| u >= def)
.max()
.copied()
.unwrap_or(def);
interval.add_segment(def, furthest_use);
}
interval.sort_and_merge();
}
}
fn infer_reg_class(&self, vreg: u32) -> RegClassKind {
if vreg >= 64 && vreg < 96 {
RegClassKind::VecReg
} else if vreg >= 32 && vreg < 64 {
RegClassKind::FPR64
} else {
RegClassKind::GPR
}
}
pub fn allocate(&mut self) -> LinearScanStats {
self.stats = LinearScanStats::default();
self.stats.total_intervals = self.intervals.len();
let mut unhandled: Vec<LinearLiveInterval> = self
.intervals
.values()
.filter(|i| i.assigned_reg.is_none() && !i.is_fixed)
.cloned()
.collect();
unhandled.sort_by(|a, b| {
a.start_point().cmp(&b.start_point()).then_with(|| {
b.spill_weight
.partial_cmp(&a.spill_weight)
.unwrap_or(Ordering::Equal)
})
});
let mut active: Vec<ActiveEntry> = Vec::new();
let mut spilled: Vec<LinearLiveInterval> = Vec::new();
for mut interval in unhandled {
let start = interval.start_point().unwrap_or(InstrPoint::new(0, 0, 0));
self.expire_active_intervals(&mut active, start);
let class = interval.reg_class;
let available = self.get_available_for_class(class);
let free_reg = self.find_free_register(&active, &available);
if let Some(reg) = free_reg {
let assign_reg = if let Some(hint) = interval.reg_hint {
if !active.iter().any(|a| a.phys_reg == hint) && available.contains(&hint) {
hint
} else {
reg
}
} else {
reg
};
interval.assigned_reg = Some(assign_reg);
self.assignments.insert(interval.vreg, Some(assign_reg));
self.stats.registers_assigned += 1;
let end = interval.end_point().unwrap_or(start);
active.push(ActiveEntry::new(interval.clone(), assign_reg, end));
} else {
self.spill_interval(&mut interval, &mut active, start);
spilled.push(interval.clone());
}
self.allocation_count += 1;
}
for mut interval in spilled {
self.assign_spill_slot(&mut interval);
self.stats.registers_spilled += 1;
}
self.stats.clone()
}
fn expire_active_intervals(&self, active: &mut Vec<ActiveEntry>, point: InstrPoint) {
active.retain(|entry| {
entry.end_point >= point
});
}
fn find_free_register(&self, active: &[ActiveEntry], available: &[u32]) -> Option<u32> {
let used: HashSet<u32> = active.iter().map(|a| a.phys_reg).collect();
available.iter().find(|r| !used.contains(r)).copied()
}
fn get_available_for_class(&self, class: RegClassKind) -> Vec<u32> {
self.available_regs
.get(&class)
.cloned()
.unwrap_or_default()
.into_iter()
.filter(|r| !self.reserved_regs.contains(r))
.collect()
}
fn spill_interval(
&mut self,
current: &mut LinearLiveInterval,
active: &mut Vec<ActiveEntry>,
at_point: InstrPoint,
) {
if active.is_empty() {
current.spilled = true;
self.spill_count += 1;
return;
}
let mut spill_idx = 0;
let mut furthest_point = InstrPoint::new(0, 0, 0);
for (i, entry) in active.iter().enumerate() {
let last_use = entry
.interval
.furthest_use_from(at_point)
.unwrap_or(entry.end_point);
if last_use > furthest_point {
furthest_point = last_use;
spill_idx = i;
}
}
let spill_entry = &active[spill_idx];
let current_end = current.end_point().unwrap_or(at_point);
if current_end < furthest_point {
current.spilled = true;
self.spill_count += 1;
return;
}
let evicted_reg = spill_entry.phys_reg;
let mut evicted_interval = active.remove(spill_idx).interval;
evicted_interval.spilled = true;
evicted_interval.assigned_reg = None;
self.assignments.insert(evicted_interval.vreg, None);
current.assigned_reg = Some(evicted_reg);
self.assignments.insert(current.vreg, Some(evicted_reg));
self.stats.registers_assigned += 1;
self.spill_count += 1;
let end = current.end_point().unwrap_or(at_point);
active.push(ActiveEntry::new(current.clone(), evicted_reg, end));
}
fn assign_spill_slot(&mut self, interval: &mut LinearLiveInterval) {
if self.spill_slots.contains_key(&interval.vreg) {
return;
}
let slot = self.next_spill_slot;
self.next_spill_slot += 8; self.spill_slots.insert(interval.vreg, slot);
interval.spill_offset = slot;
for &def in &interval.def_points {
self.stats.store_after_def_inserted += 1;
}
for &u in &interval.use_points {
self.stats.load_before_use_inserted += 1;
}
}
pub fn try_coalesce(&mut self, dst: u32, src: u32) -> bool {
let src_reg = self.intervals.get(&src).and_then(|i| i.assigned_reg);
let dst_reg = self.intervals.get(&dst).and_then(|i| i.assigned_reg);
let src_overlaps_dst =
if let (Some(si), Some(di)) = (self.intervals.get(&src), self.intervals.get(&dst)) {
si.overlaps_with(di)
} else {
true
};
if src_overlaps_dst {
return false;
}
if let Some(reg) = dst_reg {
if let Some(src_mut) = self.intervals.get_mut(&src) {
src_mut.set_hint(reg);
self.coalesced_count += 1;
self.stats.copies_coalesced += 1;
return true;
}
}
if let Some(reg) = src_reg {
if let Some(dst_mut) = self.intervals.get_mut(&dst) {
dst_mut.set_hint(reg);
self.coalesced_count += 1;
self.stats.copies_coalesced += 1;
return true;
}
}
false
}
pub fn handle_two_address(&mut self, dst: u32, src: u32) -> bool {
if let (Some(dst_assign), Some(src_assign)) = (
self.assignments.get(&dst).copied().flatten(),
self.assignments.get(&src).copied().flatten(),
) {
if dst_assign != src_assign {
let src_interval = self.intervals.get(&src);
let dst_interval = self.intervals.get(&dst);
if let (Some(si), Some(di)) = (src_interval, dst_interval) {
if !si.overlaps_with(di) || di.start_point() > si.end_point() {
self.assignments.insert(dst, Some(src_assign));
if let Some(dst_mut) = self.intervals.get_mut(&dst) {
dst_mut.assigned_reg = Some(src_assign);
}
self.stats.two_address_resolved += 1;
return true;
}
}
}
}
false
}
pub fn get_assignment(&self, vreg: u32) -> Option<u32> {
self.assignments.get(&vreg).copied().flatten()
}
pub fn is_spilled(&self, vreg: u32) -> bool {
self.spill_slots.contains_key(&vreg)
}
pub fn get_spill_slot(&self, vreg: u32) -> Option<i32> {
self.spill_slots.get(&vreg).copied()
}
pub fn get_stats(&self) -> &LinearScanStats {
&self.stats
}
pub fn add_copy_hint(&mut self, dst: u32, src_vreg: u32) {
if let Some(src) = self.intervals.get(&src_vreg) {
if let Some(reg) = src.assigned_reg.or(src.fixed_reg) {
if let Some(dst_interval) = self.intervals.get_mut(&dst) {
dst_interval.set_hint(reg);
}
}
}
}
}
impl Default for LinearScanAllocator {
fn default() -> Self {
Self::new()
}
}
struct IntervalByStart(LinearLiveInterval);
impl PartialEq for IntervalByStart {
fn eq(&self, other: &Self) -> bool {
self.0.start_point() == other.0.start_point()
}
}
impl Eq for IntervalByStart {}
impl PartialOrd for IntervalByStart {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for IntervalByStart {
fn cmp(&self, other: &Self) -> Ordering {
self.0
.start_point()
.cmp(&other.0.start_point())
.then_with(|| {
other
.0
.spill_weight
.partial_cmp(&self.0.spill_weight)
.unwrap_or(Ordering::Equal)
})
}
}
#[derive(Debug, Clone)]
pub struct SpillCodeContext {
pub spill_slots: HashMap<u32, i32>,
pub spill_events: Vec<SpillEvent>,
pub frame_size: i32,
}
#[derive(Debug, Clone)]
pub enum SpillEvent {
Store {
vreg: u32,
offset: i32,
point: InstrPoint,
},
Load {
vreg: u32,
offset: i32,
point: InstrPoint,
},
}
impl SpillCodeContext {
pub fn new() -> Self {
Self {
spill_slots: HashMap::new(),
spill_events: Vec::new(),
frame_size: 0,
}
}
pub fn add_store(&mut self, vreg: u32, offset: i32, point: InstrPoint) {
self.spill_events.push(SpillEvent::Store {
vreg,
offset,
point,
});
}
pub fn add_load(&mut self, vreg: u32, offset: i32, point: InstrPoint) {
self.spill_events.push(SpillEvent::Load {
vreg,
offset,
point,
});
}
pub fn compute_frame_size(&mut self) {
let max_offset = self.spill_slots.values().max().copied().unwrap_or(0);
self.frame_size = max_offset + 8; }
pub fn sort_events(&mut self) {
self.spill_events.sort_by(|a, b| {
let pa = match a {
SpillEvent::Store { point, .. } | SpillEvent::Load { point, .. } => *point,
};
let pb = match b {
SpillEvent::Store { point, .. } | SpillEvent::Load { point, .. } => *point,
};
pa.cmp(&pb)
});
}
}
impl Default for SpillCodeContext {
fn default() -> Self {
Self::new()
}
}
pub struct LinearScanRegAlloc {
pub allocator: LinearScanAllocator,
pub function_name: String,
pub spill_code: SpillCodeContext,
pub success: bool,
}
impl LinearScanRegAlloc {
pub fn new(function_name: &str) -> Self {
Self {
allocator: LinearScanAllocator::new(),
function_name: function_name.to_string(),
spill_code: SpillCodeContext::new(),
success: false,
}
}
pub fn run(
&mut self,
defs: &HashMap<u32, Vec<InstrPoint>>,
uses: &HashMap<u32, Vec<InstrPoint>>,
fixed: &[(u32, u32, RegClassKind)],
copy_hints: &[(u32, u32)],
two_addr: &[(u32, u32)],
) -> LinearScanStats {
for &(vreg, phys_reg, class) in fixed {
self.allocator.add_fixed_reg(vreg, phys_reg, class);
}
self.allocator.compute_intervals(defs, uses);
for &(dst, src) in copy_hints {
self.allocator.try_coalesce(dst, src);
}
let stats = self.allocator.allocate();
for &(dst, src) in two_addr {
self.allocator.handle_two_address(dst, src);
}
for (&vreg, &offset) in &self.allocator.spill_slots {
self.spill_code.spill_slots.insert(vreg, offset);
if let Some(interval) = self.allocator.intervals.get(&vreg) {
for &def in &interval.def_points {
self.spill_code.add_store(vreg, offset, def.after());
}
for &u in &interval.use_points {
self.spill_code.add_load(vreg, offset, u.before());
}
}
}
self.spill_code.sort_events();
self.spill_code.compute_frame_size();
self.success = true;
stats
}
pub fn get_assignment(&self, vreg: u32) -> Option<u32> {
self.allocator.get_assignment(vreg)
}
pub fn is_spilled(&self, vreg: u32) -> bool {
self.allocator.is_spilled(vreg)
}
}
impl Default for LinearScanRegAlloc {
fn default() -> Self {
Self::new("unknown")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pt(block: u32, instr: u32) -> InstrPoint {
InstrPoint::new(block, instr, 0)
}
fn pt_use(block: u32, instr: u32) -> InstrPoint {
InstrPoint::new(block, instr, 1)
}
#[test]
fn test_pbqp_vector_new() {
let regs = vec![0, 1, 2, 3];
let v = PbqpVector::new(®s, 10.0);
assert_eq!(v.num_choices(), 4);
assert_eq!(v.get_cost(0), Some(10.0));
assert_eq!(v.get_cost(3), Some(10.0));
assert!(v.get_cost(99).is_none());
}
#[test]
fn test_pbqp_vector_min_cost() {
let regs = vec![0, 1, 2, 3];
let mut v = PbqpVector::new(®s, 100.0);
v.set_cost(2, 5.0);
assert_eq!(v.min_cost_reg(), Some((2, 5.0)));
}
#[test]
fn test_pbqp_vector_has_finite() {
let regs = vec![0, 1];
let mut v = PbqpVector::new(®s, PBQP_INF);
assert!(!v.has_finite_cost());
v.set_cost(0, 10.0);
assert!(v.has_finite_cost());
}
#[test]
fn test_pbqp_matrix_interference() {
let m = PbqpMatrix::interference_matrix(3);
assert!(m.get(0, 0) > PBQP_INF / 2.0);
assert!(m.get(1, 1) > PBQP_INF / 2.0);
assert!(m.get(0, 1) < 1.0);
assert!(m.get(1, 0) < 1.0);
}
#[test]
fn test_pbqp_matrix_identity_benefit() {
let m = PbqpMatrix::identity_with_benefit(3, 10.0);
assert!((m.get(0, 0) + 10.0).abs() < 0.001);
assert!((m.get(1, 1) + 10.0).abs() < 0.001);
assert_eq!(m.get(0, 1), 0.0);
}
#[test]
fn test_pbqp_graph_add_node() {
let mut g = PbqpGraph::new();
let node = PbqpNode::new(0, &[0, 1, 2], 0.0);
g.add_node(node);
assert!(g.nodes.contains_key(&0));
assert_eq!(g.degree(0), 0);
}
#[test]
fn test_pbqp_graph_add_edge() {
let mut g = PbqpGraph::new();
g.add_node(PbqpNode::new(0, &[0, 1, 2], 0.0));
g.add_node(PbqpNode::new(1, &[0, 1, 2], 0.0));
let edge = PbqpEdge::new(0, 1, 3, 3);
g.add_edge(edge);
assert_eq!(g.degree(0), 1);
assert_eq!(g.degree(1), 1);
}
#[test]
fn test_pbqp_solver_r0() {
let mut solver = PbqpSolver::new();
solver.graph.add_node(PbqpNode::new(0, &[0, 1], 10.0));
solver.reduce_graph();
assert!(solver.graph.nodes.is_empty());
}
#[test]
fn test_pbqp_solver_r1() {
let mut solver = PbqpSolver::new();
solver.graph.add_node(PbqpNode::new(0, &[0, 1], 10.0));
solver.graph.add_node(PbqpNode::new(1, &[0, 1], 20.0));
let edge = PbqpEdge::new(0, 1, 2, 2);
solver.graph.add_edge(edge);
solver.reduce_graph();
assert!(solver.graph.nodes.is_empty());
}
#[test]
fn test_pbqp_reg_alloc_basic() {
let mut alloc = PbqpRegAlloc::new(vec![0, 1, 2, 3]);
alloc.allocate("test_func", 4);
assert!(!alloc.assignments.is_empty() || !alloc.spilled.is_empty());
}
#[test]
fn test_pbqp_solver_solve() {
let mut solver = PbqpSolver::new();
let vregs = vec![0, 1];
let allowed = vec![0, 1];
solver.init_graph(&vregs, &allowed, &[(0, 1)], &[]);
let solution = solver.solve();
assert_eq!(solution.len(), 2);
}
#[test]
fn test_pbqp_spill_selection() {
let mut solver = PbqpSolver::new();
let vregs = vec![0, 1];
let allowed = vec![0]; solver.init_graph(&vregs, &allowed, &[(0, 1)], &[]);
let solution = solver.solve();
let spilled_count = solution.values().filter(|s| s.spilled).count();
assert!(spilled_count >= 1);
}
#[test]
fn test_linear_scan_allocator_new() {
let alloc = LinearScanAllocator::new();
assert!(alloc.available_regs.contains_key(&RegClassKind::GPR));
assert!(alloc.available_regs.contains_key(&RegClassKind::FPR64));
}
#[test]
fn test_linear_scan_build_interval() {
let mut alloc = LinearScanAllocator::new();
let iv = alloc.build_interval(1, RegClassKind::GPR);
assert_eq!(iv.vreg, 1);
assert!(!iv.is_fixed);
}
#[test]
fn test_linear_scan_fixed_register() {
let mut alloc = LinearScanAllocator::new();
alloc.add_fixed_reg(10, 5, RegClassKind::GPR);
assert!(alloc.fixed_regs.contains_key(&10));
if let Some(iv) = alloc.intervals.get(&10) {
assert!(iv.is_fixed);
assert_eq!(iv.fixed_reg, Some(5));
}
}
#[test]
fn test_linear_scan_compute_intervals() {
let mut alloc = LinearScanAllocator::new();
let mut defs = HashMap::new();
let mut uses = HashMap::new();
defs.insert(1, vec![InstrPoint::new(0, 0, 0)]);
uses.insert(1, vec![InstrPoint::new(0, 5, 1)]);
alloc.compute_intervals(&defs, &uses);
if let Some(iv) = alloc.intervals.get(&1) {
assert!(!iv.segments.is_empty());
assert_eq!(iv.def_points.len(), 1);
assert_eq!(iv.use_points.len(), 1);
}
}
#[test]
fn test_linear_scan_interval_live_at() {
let mut iv = LinearLiveInterval::new(1, RegClassKind::GPR);
iv.add_segment(InstrPoint::new(0, 0, 0), InstrPoint::new(0, 10, 1));
assert!(iv.live_at(InstrPoint::new(0, 5, 0)));
assert!(!iv.live_at(InstrPoint::new(0, 15, 0)));
}
#[test]
fn test_linear_scan_interval_overlap() {
let mut iv1 = LinearLiveInterval::new(1, RegClassKind::GPR);
iv1.add_segment(InstrPoint::new(0, 0, 0), InstrPoint::new(0, 10, 1));
let mut iv2 = LinearLiveInterval::new(2, RegClassKind::GPR);
iv2.add_segment(InstrPoint::new(0, 5, 0), InstrPoint::new(0, 15, 1));
assert!(iv1.overlaps_with(&iv2));
let mut iv3 = LinearLiveInterval::new(3, RegClassKind::GPR);
iv3.add_segment(InstrPoint::new(0, 20, 0), InstrPoint::new(0, 30, 1));
assert!(!iv1.overlaps_with(&iv3));
}
#[test]
fn test_linear_scan_sort_and_merge() {
let mut iv = LinearLiveInterval::new(1, RegClassKind::GPR);
iv.add_segment(InstrPoint::new(0, 0, 0), InstrPoint::new(0, 5, 1));
iv.add_segment(InstrPoint::new(0, 3, 0), InstrPoint::new(0, 10, 1));
iv.sort_and_merge();
assert_eq!(iv.segments.len(), 1);
assert_eq!(iv.segments[0].start, InstrPoint::new(0, 0, 0));
assert_eq!(iv.segments[0].end, InstrPoint::new(0, 10, 1));
}
#[test]
fn test_linear_scan_allocate_simple() {
let mut alloc = LinearScanAllocator::new();
alloc
.available_regs
.insert(RegClassKind::GPR, vec![10, 11, 12]);
let mut defs = HashMap::new();
let mut uses = HashMap::new();
defs.insert(1, vec![InstrPoint::new(0, 0, 0)]);
uses.insert(1, vec![InstrPoint::new(0, 2, 1)]);
defs.insert(2, vec![InstrPoint::new(0, 3, 0)]);
uses.insert(2, vec![InstrPoint::new(0, 5, 1)]);
alloc.compute_intervals(&defs, &uses);
alloc.allocate();
assert!(alloc.stats.registers_assigned >= 1);
}
#[test]
fn test_linear_scan_spill_under_pressure() {
let mut alloc = LinearScanAllocator::new();
alloc.available_regs.insert(RegClassKind::GPR, vec![10]);
let mut defs = HashMap::new();
let mut uses = HashMap::new();
for i in 0..5 {
defs.insert(i, vec![InstrPoint::new(0, i * 2, 0)]);
uses.insert(i, vec![InstrPoint::new(0, i * 2 + 1, 1)]);
}
alloc.compute_intervals(&defs, &uses);
alloc.allocate();
let spilled_count = alloc.assignments.values().filter(|a| a.is_none()).count();
assert!(spilled_count > 0 || alloc.stats.registers_spilled > 0);
}
#[test]
fn test_linear_scan_coalesce_hint() {
let mut alloc = LinearScanAllocator::new();
alloc.available_regs.insert(RegClassKind::GPR, vec![10, 11]);
let mut defs = HashMap::new();
let mut uses = HashMap::new();
defs.insert(1, vec![InstrPoint::new(0, 0, 0)]);
uses.insert(1, vec![InstrPoint::new(0, 1, 1)]);
defs.insert(2, vec![InstrPoint::new(0, 2, 0)]);
uses.insert(2, vec![InstrPoint::new(0, 3, 1)]);
alloc.compute_intervals(&defs, &uses);
if let Some(iv) = alloc.intervals.get_mut(&1) {
iv.assigned_reg = Some(10);
}
alloc.try_coalesce(2, 1);
if let Some(iv) = alloc.intervals.get(&2) {
assert_eq!(iv.reg_hint, Some(10));
}
}
#[test]
fn test_linear_scan_two_address() {
let mut alloc = LinearScanAllocator::new();
alloc.available_regs.insert(RegClassKind::GPR, vec![10, 11]);
let mut defs = HashMap::new();
let mut uses = HashMap::new();
defs.insert(1, vec![InstrPoint::new(0, 0, 0)]);
uses.insert(1, vec![InstrPoint::new(0, 1, 1)]);
defs.insert(2, vec![InstrPoint::new(0, 2, 0)]);
uses.insert(2, vec![InstrPoint::new(0, 3, 1)]);
alloc.compute_intervals(&defs, &uses);
alloc.assignments.insert(1, Some(10));
alloc.assignments.insert(2, Some(11));
if let Some(iv) = alloc.intervals.get_mut(&1) {
iv.assigned_reg = Some(10);
}
if let Some(iv) = alloc.intervals.get_mut(&2) {
iv.assigned_reg = Some(11);
}
alloc.handle_two_address(2, 1);
assert!(alloc.stats.two_address_resolved >= 0);
}
#[test]
fn test_linear_scan_reg_class_constraint() {
let mut alloc = LinearScanAllocator::new();
let class = RegClassKind::GPR;
alloc.available_regs.insert(class, vec![10, 11]);
let mut defs = HashMap::new();
let mut uses = HashMap::new();
defs.insert(1, vec![InstrPoint::new(0, 0, 0)]);
uses.insert(1, vec![InstrPoint::new(0, 1, 1)]);
alloc.compute_intervals(&defs, &uses);
alloc.allocate();
if let Some(assignment) = alloc.get_assignment(1) {
let available = alloc.available_regs.get(&RegClassKind::GPR).unwrap();
assert!(available.contains(&assignment));
}
}
#[test]
fn test_linear_scan_spill_code_context() {
let mut ctx = SpillCodeContext::new();
let point = InstrPoint::new(0, 5, 0);
ctx.add_store(1, 0, point.after());
ctx.add_load(1, 0, point.before());
assert_eq!(ctx.spill_events.len(), 2);
ctx.sort_events();
ctx.compute_frame_size();
assert!(ctx.frame_size >= 0);
}
#[test]
fn test_linear_scan_reg_alloc_run() {
let mut alloc = LinearScanRegAlloc::new("test");
let mut defs = HashMap::new();
let mut uses = HashMap::new();
defs.insert(1, vec![InstrPoint::new(0, 0, 0)]);
uses.insert(1, vec![InstrPoint::new(0, 2, 1)]);
let fixed = &[];
let hints = &[];
let two_addr = &[];
let stats = alloc.run(&defs, &uses, fixed, hints, two_addr);
assert!(alloc.success);
assert!(stats.total_intervals >= 1);
}
#[test]
fn test_linear_scan_instr_point_ordering() {
let p1 = InstrPoint::new(0, 0, 0);
let p2 = InstrPoint::new(0, 0, 1);
let p3 = InstrPoint::new(0, 1, 0);
let p4 = InstrPoint::new(1, 0, 0);
assert!(p1 < p2);
assert!(p2 < p3);
assert!(p3 < p4);
}
#[test]
fn test_linear_scan_instr_point_before_after() {
let p = InstrPoint::new(0, 5, 0);
assert!(p.before() <= p);
assert!(p.after() >= p);
}
#[test]
fn test_linear_scan_reserved_regs_not_used() {
let mut alloc = LinearScanAllocator::new();
alloc.available_regs.insert(RegClassKind::GPR, vec![0, 2]);
let mut defs = HashMap::new();
let mut uses = HashMap::new();
defs.insert(1, vec![InstrPoint::new(0, 0, 0)]);
uses.insert(1, vec![InstrPoint::new(0, 1, 1)]);
alloc.compute_intervals(&defs, &uses);
alloc.allocate();
if let Some(Some(assigned)) = alloc.assignments.get(&1) {
assert!(!alloc.reserved_regs.contains(assigned));
}
}
#[test]
fn test_linear_scan_spill_weight_computation() {
let mut iv = LinearLiveInterval::new(1, RegClassKind::GPR);
iv.add_segment(InstrPoint::new(0, 0, 0), InstrPoint::new(0, 10, 1));
iv.add_use(InstrPoint::new(0, 2, 1));
iv.add_use(InstrPoint::new(0, 8, 1));
iv.add_def(InstrPoint::new(0, 0, 0));
iv.compute_spill_weight(0); assert!(iv.spill_weight > 0.0);
iv.compute_spill_weight(3); assert!(iv.spill_weight > 0.0);
}
#[test]
fn test_linear_scan_active_list_expiry() {
let mut alloc = LinearScanAllocator::new();
let point = InstrPoint::new(0, 10, 0);
let mut active = vec![
ActiveEntry::new(
LinearLiveInterval::new(1, RegClassKind::GPR),
10,
InstrPoint::new(0, 5, 1), ),
ActiveEntry::new(
LinearLiveInterval::new(2, RegClassKind::GPR),
11,
InstrPoint::new(0, 15, 1), ),
];
alloc.expire_active_intervals(&mut active, point);
assert_eq!(active.len(), 1);
}
}
pub use llvm_native_core::codegen::RegAllocResult;