use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use super::asm_ast::*;
use crate::parse::ast::Type;
pub struct RegAllocResult {
pub instructions: Vec<Instruction>,
pub spill_bytes: usize,
pub callee_saved_used: Vec<Reg>,
}
pub fn allocate_registers(
instructions: Vec<Instruction>,
var_types: &HashMap<String, Type>,
) -> RegAllocResult {
let (gp_pseudos, xmm_pseudos) = classify_pseudos(&instructions, var_types);
let liveness = analyze_liveness(&instructions);
let gp_coloring = if !gp_pseudos.is_empty() {
let mut graph = build_interference_graph(&instructions, &liveness, &gp_pseudos, false);
let merge_map = coalesce_graph(&mut graph, GP_ALLOCATABLE.len());
let mut coloring = color_graph(graph, &GP_ALLOCATABLE, false);
apply_merge_map(&mut coloring, &merge_map);
coloring
} else {
ColoringResult {
assignments: HashMap::new(),
}
};
let xmm_coloring = if !xmm_pseudos.is_empty() {
let mut graph = build_interference_graph(&instructions, &liveness, &xmm_pseudos, true);
let merge_map = coalesce_graph(&mut graph, XMM_ALLOCATABLE.len());
let mut coloring = color_graph(graph, &XMM_ALLOCATABLE, true);
apply_merge_map(&mut coloring, &merge_map);
coloring
} else {
ColoringResult {
assignments: HashMap::new(),
}
};
let mut assignments: HashMap<String, Operand> = HashMap::new();
let mut callee_saved_set: HashSet<Reg> = HashSet::new();
let mut next_spill_offset: i32 = scan_min_stack_offset(&instructions);
let mut gp_keys: Vec<&String> = gp_coloring.assignments.keys().collect();
gp_keys.sort();
for name in gp_keys {
let assignment = &gp_coloring.assignments[name];
match assignment {
Assignment::Register(reg) => {
assignments.insert(name.clone(), Operand::Register(*reg));
if GP_CALLEE_SAVED.contains(reg) {
callee_saved_set.insert(*reg);
}
}
Assignment::Spill => {
next_spill_offset -= 8;
assignments.insert(name.clone(), Operand::Stack(next_spill_offset));
}
}
}
let mut xmm_keys: Vec<&String> = xmm_coloring.assignments.keys().collect();
xmm_keys.sort();
for name in xmm_keys {
let assignment = &xmm_coloring.assignments[name];
match assignment {
Assignment::Register(reg) => {
assignments.insert(name.clone(), Operand::Register(*reg));
}
Assignment::Spill => {
next_spill_offset -= 8;
assignments.insert(name.clone(), Operand::Stack(next_spill_offset));
}
}
}
let instructions = replace_pseudos(instructions, &assignments);
let mut callee_saved_used: Vec<Reg> = callee_saved_set.into_iter().collect();
callee_saved_used.sort_by_key(|r| GP_CALLEE_SAVED.iter().position(|x| x == r).unwrap_or(99));
RegAllocResult {
instructions,
spill_bytes: (-next_spill_offset) as usize,
callee_saved_used,
}
}
pub fn fixup_instructions(
instructions: Vec<Instruction>,
spill_bytes: usize,
callee_saved_used: &[Reg],
) -> Vec<Instruction> {
let mut fixed = Vec::new();
for instr in instructions {
fixup_instruction(instr, &mut fixed);
}
insert_prologue_epilogue(fixed, spill_bytes, callee_saved_used)
}
fn classify_pseudos(
instructions: &[Instruction],
var_types: &HashMap<String, Type>,
) -> (HashSet<String>, HashSet<String>) {
let mut gp = HashSet::new();
let mut xmm = HashSet::new();
for instr in instructions {
for_each_operand(instr, |op| {
if let Operand::Pseudo(name) = op {
let ty = var_types.get(name);
if ty.is_some_and(|t| t.is_floating()) {
xmm.insert(name.clone());
} else {
gp.insert(name.clone());
}
}
});
}
(gp, xmm)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum GraphNode {
Pseudo(String),
HardReg(Reg),
}
struct LivenessInfo {
live_after: Vec<HashSet<GraphNode>>,
}
fn instruction_uses(instr: &Instruction) -> Vec<GraphNode> {
let mut uses = Vec::new();
match instr {
Instruction::Mov { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Unary { operand, .. } => {
add_operand_nodes_for_use(operand, &mut uses);
}
Instruction::Cmp { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_operand_nodes_for_use(dst, &mut uses);
}
Instruction::SetCC { operand, .. } => {
add_memory_base_to_uses(operand, &mut uses);
}
Instruction::Binary { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_operand_nodes_for_use(dst, &mut uses);
}
Instruction::Idiv { operand, .. } => {
add_operand_nodes_for_use(operand, &mut uses);
uses.push(GraphNode::HardReg(Reg::AX));
uses.push(GraphNode::HardReg(Reg::DX));
}
Instruction::Div { operand, .. } => {
add_operand_nodes_for_use(operand, &mut uses);
uses.push(GraphNode::HardReg(Reg::AX));
uses.push(GraphNode::HardReg(Reg::DX));
}
Instruction::SignExtend(_) => {
uses.push(GraphNode::HardReg(Reg::AX));
}
Instruction::Movsx { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::MovsxByte { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::MovZeroExtend { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::MovZeroExtendByte { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::MovsxWord { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::MovZeroExtendWord { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Truncate { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Push(op) => {
add_operand_nodes_for_use(op, &mut uses);
}
Instruction::Pop(op) => {
add_memory_base_to_uses(op, &mut uses);
}
Instruction::Jmp(_) | Instruction::JmpCC(_, _) | Instruction::Label(_) => {}
Instruction::AllocateStack(_) | Instruction::DeallocateStack(_) => {}
Instruction::Call(_) => {}
Instruction::Ret => {
uses.push(GraphNode::HardReg(Reg::AX));
}
Instruction::Cvtsi2sd { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Cvttsd2si { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Cvtsi2ss { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Cvttss2si { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Cvtss2sd { src, dst } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Cvtsd2ss { src, dst } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::Lea { src, dst, .. } => {
add_operand_nodes_for_use(src, &mut uses);
add_memory_base_to_uses(dst, &mut uses);
}
Instruction::JmpIndirect(op, _) => {
add_operand_nodes_for_use(op, &mut uses);
}
Instruction::CallIndirect(op) => {
add_operand_nodes_for_use(op, &mut uses);
}
Instruction::RawBytes(_) => {}
}
uses
}
fn instruction_defs(instr: &Instruction) -> Vec<GraphNode> {
let mut defs = Vec::new();
match instr {
Instruction::Mov { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Unary { operand, .. } => {
add_operand_nodes_for_def(operand, &mut defs);
}
Instruction::Cmp { .. } => {}
Instruction::SetCC { operand, .. } => {
add_operand_nodes_for_def(operand, &mut defs);
}
Instruction::Binary { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Idiv { .. } => {
defs.push(GraphNode::HardReg(Reg::AX));
defs.push(GraphNode::HardReg(Reg::DX));
}
Instruction::Div { .. } => {
defs.push(GraphNode::HardReg(Reg::AX));
defs.push(GraphNode::HardReg(Reg::DX));
}
Instruction::SignExtend(_) => {
defs.push(GraphNode::HardReg(Reg::DX));
}
Instruction::Movsx { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::MovsxByte { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::MovZeroExtend { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::MovZeroExtendByte { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::MovsxWord { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::MovZeroExtendWord { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Truncate { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Push(_) => {}
Instruction::Pop(op) => {
add_operand_nodes_for_def(op, &mut defs);
}
Instruction::Jmp(_) | Instruction::JmpCC(_, _) | Instruction::Label(_) => {}
Instruction::AllocateStack(_) | Instruction::DeallocateStack(_) => {}
Instruction::Call(_) => {
for ® in &GP_CALLER_SAVED {
defs.push(GraphNode::HardReg(reg));
}
for ® in &XMM_ALL {
defs.push(GraphNode::HardReg(reg));
}
}
Instruction::Ret => {}
Instruction::Cvtsi2sd { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Cvttsd2si { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Cvtsi2ss { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Cvttss2si { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Cvtss2sd { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Cvtsd2ss { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::Lea { dst, .. } => {
add_operand_nodes_for_def(dst, &mut defs);
}
Instruction::JmpIndirect(_, _) => {}
Instruction::CallIndirect(_) => {
for ® in &GP_CALLER_SAVED {
defs.push(GraphNode::HardReg(reg));
}
for ® in &XMM_ALL {
defs.push(GraphNode::HardReg(reg));
}
}
Instruction::RawBytes(_) => {}
}
defs
}
fn add_operand_nodes_for_use(op: &Operand, nodes: &mut Vec<GraphNode>) {
match op {
Operand::Register(reg) => nodes.push(GraphNode::HardReg(*reg)),
Operand::Pseudo(name) => nodes.push(GraphNode::Pseudo(name.clone())),
Operand::Memory(reg) => nodes.push(GraphNode::HardReg(*reg)),
Operand::MemoryOffset(reg, _) => nodes.push(GraphNode::HardReg(*reg)),
Operand::Imm(_) | Operand::Stack(_) | Operand::Data(_) => {}
}
}
fn add_operand_nodes_for_def(op: &Operand, nodes: &mut Vec<GraphNode>) {
match op {
Operand::Register(reg) => nodes.push(GraphNode::HardReg(*reg)),
Operand::Pseudo(name) => nodes.push(GraphNode::Pseudo(name.clone())),
Operand::Memory(_) | Operand::MemoryOffset(_, _) => {
}
Operand::Imm(_) | Operand::Stack(_) | Operand::Data(_) => {}
}
}
fn add_memory_base_to_uses(op: &Operand, nodes: &mut Vec<GraphNode>) {
match op {
Operand::Memory(reg) => nodes.push(GraphNode::HardReg(*reg)),
Operand::MemoryOffset(reg, _) => nodes.push(GraphNode::HardReg(*reg)),
_ => {}
}
}
fn jump_targets(instr: &Instruction) -> Vec<String> {
match instr {
Instruction::Jmp(label) => vec![label.clone()],
Instruction::JmpCC(_, label) => vec![label.clone()],
Instruction::JmpIndirect(_, targets) => targets.clone(),
_ => vec![],
}
}
fn analyze_liveness(instructions: &[Instruction]) -> LivenessInfo {
let n = instructions.len();
if n == 0 {
return LivenessInfo { live_after: vec![] };
}
let mut label_to_idx: HashMap<String, usize> = HashMap::new();
for (i, instr) in instructions.iter().enumerate() {
if let Instruction::Label(label) = instr {
label_to_idx.insert(label.clone(), i);
}
}
let mut successors: Vec<Vec<usize>> = vec![vec![]; n];
for i in 0..n {
let targets = jump_targets(&instructions[i]);
for target in &targets {
if let Some(&idx) = label_to_idx.get(target) {
successors[i].push(idx);
}
}
if !matches!(
instructions[i],
Instruction::Jmp(_) | Instruction::JmpIndirect(_, _) | Instruction::Ret
) && i + 1 < n
{
successors[i].push(i + 1);
}
}
let uses: Vec<HashSet<GraphNode>> = instructions
.iter()
.map(|instr| instruction_uses(instr).into_iter().collect())
.collect();
let defs: Vec<HashSet<GraphNode>> = instructions
.iter()
.map(|instr| instruction_defs(instr).into_iter().collect())
.collect();
let mut live_in: Vec<HashSet<GraphNode>> = vec![HashSet::new(); n];
let mut live_after: Vec<HashSet<GraphNode>> = vec![HashSet::new(); n];
let mut changed = true;
while changed {
changed = false;
for i in (0..n).rev() {
let mut new_after: HashSet<GraphNode> = HashSet::new();
for &s in &successors[i] {
for node in &live_in[s] {
new_after.insert(node.clone());
}
}
let mut new_in: HashSet<GraphNode> = uses[i].clone();
for node in &new_after {
if !defs[i].contains(node) {
new_in.insert(node.clone());
}
}
if new_in != live_in[i] || new_after != live_after[i] {
changed = true;
live_in[i] = new_in;
live_after[i] = new_after;
}
}
}
LivenessInfo { live_after }
}
struct InterferenceGraph {
adj: BTreeMap<GraphNode, BTreeSet<GraphNode>>,
mov_edges: Vec<(GraphNode, GraphNode)>,
}
impl InterferenceGraph {
fn new() -> Self {
InterferenceGraph {
adj: BTreeMap::new(),
mov_edges: Vec::new(),
}
}
fn add_node(&mut self, node: GraphNode) {
self.adj.entry(node).or_default();
}
fn add_edge(&mut self, a: &GraphNode, b: &GraphNode) {
if a == b {
return;
}
self.adj.entry(a.clone()).or_default().insert(b.clone());
self.adj.entry(b.clone()).or_default().insert(a.clone());
}
}
fn build_interference_graph(
instructions: &[Instruction],
liveness: &LivenessInfo,
relevant_pseudos: &HashSet<String>,
is_xmm: bool,
) -> InterferenceGraph {
let mut graph = InterferenceGraph::new();
let relevant_hard_regs: HashSet<Reg> = if is_xmm {
XMM_ALL.iter().copied().collect()
} else {
let mut s: HashSet<Reg> = GP_ALLOCATABLE.iter().copied().collect();
s.insert(Reg::R10);
s.insert(Reg::R11);
s
};
let is_relevant = |node: &GraphNode| -> bool {
match node {
GraphNode::Pseudo(name) => relevant_pseudos.contains(name),
GraphNode::HardReg(reg) => relevant_hard_regs.contains(reg),
}
};
for name in relevant_pseudos {
graph.add_node(GraphNode::Pseudo(name.clone()));
}
for (i, instr) in instructions.iter().enumerate() {
let defined = instruction_defs(instr);
let live_after = &liveness.live_after[i];
let is_mov = matches!(
instr,
Instruction::Mov { .. }
| Instruction::Movsx { .. }
| Instruction::MovsxByte { .. }
| Instruction::MovsxWord { .. }
| Instruction::MovZeroExtend { .. }
| Instruction::MovZeroExtendByte { .. }
| Instruction::MovZeroExtendWord { .. }
| Instruction::Truncate { .. }
);
let mov_src: Option<GraphNode> = if is_mov {
let uses = instruction_uses(instr);
uses.into_iter().next()
} else {
None
};
for d in &defined {
if !is_relevant(d) {
continue;
}
graph.add_node(d.clone());
for v in live_after {
if !is_relevant(v) {
continue;
}
if v == d {
continue;
}
if is_mov
&& let Some(ref src_node) = mov_src
&& v == src_node
{
continue;
}
graph.add_edge(d, v);
}
}
if matches!(instr, Instruction::Mov { .. })
&& let Some(ref src_node) = mov_src
{
for d in &defined {
if is_relevant(src_node) && is_relevant(d) && src_node != d {
graph.mov_edges.push((src_node.clone(), d.clone()));
}
}
}
}
graph
}
fn find_canonical(merge_map: &HashMap<GraphNode, GraphNode>, node: &GraphNode) -> GraphNode {
let mut current = node.clone();
while let Some(next) = merge_map.get(¤t) {
current = next.clone();
}
current
}
fn coalesce_graph(graph: &mut InterferenceGraph, k: usize) -> HashMap<GraphNode, GraphNode> {
let mut merge_map: HashMap<GraphNode, GraphNode> = HashMap::new();
let mut changed = true;
while changed {
changed = false;
let mov_edges_snapshot = graph.mov_edges.clone();
for (raw_u, raw_v) in &mov_edges_snapshot {
let u = find_canonical(&merge_map, raw_u);
let v = find_canonical(&merge_map, raw_v);
if u == v {
continue;
}
if graph.adj.get(&u).is_some_and(|s| s.contains(&v)) {
continue;
}
let can_coalesce = match (&u, &v) {
(GraphNode::Pseudo(_), GraphNode::Pseudo(_)) => briggs_criterion(graph, &u, &v, k),
(GraphNode::Pseudo(_), GraphNode::HardReg(_)) => george_criterion(graph, &u, &v, k),
(GraphNode::HardReg(_), GraphNode::Pseudo(_)) => george_criterion(graph, &v, &u, k),
(GraphNode::HardReg(_), GraphNode::HardReg(_)) => false,
};
if can_coalesce {
let (from, into) = match (&u, &v) {
(GraphNode::HardReg(_), _) => (v.clone(), u.clone()),
(_, GraphNode::HardReg(_)) => (u.clone(), v.clone()),
_ => (u.clone(), v.clone()),
};
merge_nodes(graph, &from, &into);
merge_map.insert(from, into);
changed = true;
break; }
}
}
merge_map
}
fn briggs_criterion(graph: &InterferenceGraph, u: &GraphNode, v: &GraphNode, k: usize) -> bool {
let u_neighbors = graph.adj.get(u).cloned().unwrap_or_default();
let v_neighbors = graph.adj.get(v).cloned().unwrap_or_default();
let mut merged_neighbors: BTreeSet<GraphNode> = u_neighbors;
merged_neighbors.extend(v_neighbors);
merged_neighbors.remove(u);
merged_neighbors.remove(v);
let high_degree_count = merged_neighbors
.iter()
.filter(|n| match n {
GraphNode::HardReg(_) => true, GraphNode::Pseudo(_) => graph.adj.get(n).map_or(0, |s| s.len()) >= k,
})
.count();
high_degree_count < k
}
fn george_criterion(
graph: &InterferenceGraph,
u: &GraphNode, v: &GraphNode, k: usize,
) -> bool {
let u_neighbors = graph.adj.get(u).cloned().unwrap_or_default();
let v_neighbors = graph.adj.get(v).cloned().unwrap_or_default();
for t in &u_neighbors {
if t == v {
continue;
}
if v_neighbors.contains(t) {
continue;
} match t {
GraphNode::HardReg(_) => return false, GraphNode::Pseudo(_) => {
if graph.adj.get(t).map_or(0, |s| s.len()) >= k {
return false; }
}
}
}
true
}
fn merge_nodes(graph: &mut InterferenceGraph, from: &GraphNode, into: &GraphNode) {
let from_neighbors = graph.adj.remove(from).unwrap_or_default();
for n in &from_neighbors {
if n == into {
continue;
}
if let Some(s) = graph.adj.get_mut(n) {
s.remove(from);
s.insert(into.clone());
}
graph.adj.entry(into.clone()).or_default().insert(n.clone());
}
if let Some(s) = graph.adj.get_mut(into) {
s.remove(from);
}
}
fn apply_merge_map(coloring: &mut ColoringResult, merge_map: &HashMap<GraphNode, GraphNode>) {
let mut entries: Vec<_> = merge_map.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (from, into) in entries {
if let GraphNode::Pseudo(from_name) = from {
let canonical = find_canonical(merge_map, into);
match &canonical {
GraphNode::Pseudo(canonical_name) => {
if let Some(assignment) = coloring.assignments.get(canonical_name).cloned() {
coloring.assignments.insert(from_name.clone(), assignment);
}
}
GraphNode::HardReg(reg) => {
coloring
.assignments
.insert(from_name.clone(), Assignment::Register(*reg));
}
}
}
}
}
#[derive(Debug, Clone)]
enum Assignment {
Register(Reg),
Spill,
}
struct ColoringResult {
assignments: HashMap<String, Assignment>,
}
fn color_graph(graph: InterferenceGraph, allocatable: &[Reg], _is_xmm: bool) -> ColoringResult {
let k = allocatable.len();
let original_adj = graph.adj.clone();
let mut pseudo_nodes: Vec<String> = graph
.adj
.keys()
.filter_map(|node| {
if let GraphNode::Pseudo(name) = node {
Some(name.clone())
} else {
None
}
})
.collect();
pseudo_nodes.sort();
if pseudo_nodes.is_empty() {
return ColoringResult {
assignments: HashMap::new(),
};
}
let mut working_adj: BTreeMap<GraphNode, BTreeSet<GraphNode>> = graph.adj;
let mut stack: Vec<(String, bool)> = Vec::new();
let mut remaining: BTreeSet<String> = pseudo_nodes.into_iter().collect();
let working_degree = |adj: &BTreeMap<GraphNode, BTreeSet<GraphNode>>, name: &str| -> usize {
let node = GraphNode::Pseudo(name.to_string());
adj.get(&node).map_or(0, |s| s.len())
};
let remove_from_working = |adj: &mut BTreeMap<GraphNode, BTreeSet<GraphNode>>, name: &str| {
let node = GraphNode::Pseudo(name.to_string());
if let Some(neighbors) = adj.remove(&node) {
for n in &neighbors {
if let Some(s) = adj.get_mut(n) {
s.remove(&node);
}
}
}
};
while !remaining.is_empty() {
let mut progress = true;
while progress {
progress = false;
let candidates: Vec<String> = remaining
.iter()
.filter(|name| working_degree(&working_adj, name) < k)
.cloned()
.collect();
for name in candidates {
remove_from_working(&mut working_adj, &name);
stack.push((name.clone(), false));
remaining.remove(&name);
progress = true;
}
}
if !remaining.is_empty() {
let spill_name = remaining
.iter()
.max_by_key(|name| working_degree(&working_adj, name))
.cloned()
.unwrap();
remove_from_working(&mut working_adj, &spill_name);
stack.push((spill_name.clone(), true));
remaining.remove(&spill_name);
}
}
let mut assignments: HashMap<String, Assignment> = HashMap::new();
let reg_to_color: HashMap<Reg, usize> = allocatable
.iter()
.enumerate()
.map(|(i, ®)| (reg, i))
.collect();
while let Some((name, _is_spill_candidate)) = stack.pop() {
let node = GraphNode::Pseudo(name.clone());
let original_neighbors = original_adj.get(&node).cloned().unwrap_or_default();
let mut used_colors: HashSet<usize> = HashSet::new();
for neighbor in &original_neighbors {
match neighbor {
GraphNode::HardReg(reg) => {
if let Some(&color) = reg_to_color.get(reg) {
used_colors.insert(color);
}
}
GraphNode::Pseudo(n) => {
if let Some(Assignment::Register(reg)) = assignments.get(n)
&& let Some(&color) = reg_to_color.get(reg)
{
used_colors.insert(color);
}
}
}
}
let mut assigned = false;
for (color, ®) in allocatable.iter().enumerate() {
if !used_colors.contains(&color) {
assignments.insert(name.clone(), Assignment::Register(reg));
assigned = true;
break;
}
}
if !assigned {
assignments.insert(name.clone(), Assignment::Spill);
}
}
ColoringResult { assignments }
}
fn replace_pseudos(
instructions: Vec<Instruction>,
assignments: &HashMap<String, Operand>,
) -> Vec<Instruction> {
instructions
.into_iter()
.map(|instr| replace_in_instruction(instr, assignments))
.collect()
}
fn replace_operand(op: Operand, assignments: &HashMap<String, Operand>) -> Operand {
match op {
Operand::Pseudo(ref name) => assignments.get(name).cloned().unwrap_or(op),
_ => op,
}
}
fn replace_in_instruction(
instr: Instruction,
assignments: &HashMap<String, Operand>,
) -> Instruction {
match instr {
Instruction::Mov { asm_type, src, dst } => Instruction::Mov {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Unary {
asm_type,
op,
operand,
} => Instruction::Unary {
asm_type,
op,
operand: replace_operand(operand, assignments),
},
Instruction::Cmp { asm_type, src, dst } => Instruction::Cmp {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::SetCC { condition, operand } => Instruction::SetCC {
condition,
operand: replace_operand(operand, assignments),
},
Instruction::Binary {
asm_type,
op,
src,
dst,
} => Instruction::Binary {
asm_type,
op,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Idiv { asm_type, operand } => Instruction::Idiv {
asm_type,
operand: replace_operand(operand, assignments),
},
Instruction::Div { asm_type, operand } => Instruction::Div {
asm_type,
operand: replace_operand(operand, assignments),
},
Instruction::Movsx { src, dst } => Instruction::Movsx {
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::MovsxByte { asm_type, src, dst } => Instruction::MovsxByte {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::MovZeroExtend { src, dst } => Instruction::MovZeroExtend {
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::MovZeroExtendByte { asm_type, src, dst } => Instruction::MovZeroExtendByte {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::MovsxWord { asm_type, src, dst } => Instruction::MovsxWord {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::MovZeroExtendWord { asm_type, src, dst } => Instruction::MovZeroExtendWord {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Truncate { src, dst } => Instruction::Truncate {
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Push(op) => Instruction::Push(replace_operand(op, assignments)),
Instruction::Pop(op) => Instruction::Pop(replace_operand(op, assignments)),
Instruction::Cvtsi2sd { asm_type, src, dst } => Instruction::Cvtsi2sd {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Cvttsd2si { asm_type, src, dst } => Instruction::Cvttsd2si {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Cvtsi2ss { asm_type, src, dst } => Instruction::Cvtsi2ss {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Cvttss2si { asm_type, src, dst } => Instruction::Cvttss2si {
asm_type,
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Cvtss2sd { src, dst } => Instruction::Cvtss2sd {
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Cvtsd2ss { src, dst } => Instruction::Cvtsd2ss {
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::Lea { src, dst } => Instruction::Lea {
src: replace_operand(src, assignments),
dst: replace_operand(dst, assignments),
},
Instruction::JmpIndirect(op, ref targets) => {
Instruction::JmpIndirect(replace_operand(op, assignments), targets.clone())
}
Instruction::CallIndirect(op) => {
Instruction::CallIndirect(replace_operand(op, assignments))
}
instr @ (Instruction::SignExtend(_)
| Instruction::Jmp(_)
| Instruction::JmpCC(_, _)
| Instruction::Label(_)
| Instruction::AllocateStack(_)
| Instruction::DeallocateStack(_)
| Instruction::Call(_)
| Instruction::Ret
| Instruction::RawBytes(_)) => instr,
}
}
fn is_memory_operand(op: &Operand) -> bool {
matches!(
op,
Operand::Stack(_) | Operand::Data(_) | Operand::Memory(_) | Operand::MemoryOffset(_, _)
)
}
fn is_imm(op: &Operand) -> bool {
matches!(op, Operand::Imm(_))
}
fn is_large_imm(op: &Operand) -> bool {
if let Operand::Imm(v) = op {
*v > i32::MAX as i64 || *v < i32::MIN as i64
} else {
false
}
}
fn fixup_instruction(instr: Instruction, out: &mut Vec<Instruction>) {
match instr {
Instruction::Mov {
asm_type,
ref src,
ref dst,
} if is_memory_operand(src) && is_memory_operand(dst) => {
if asm_type == AsmType::Double || asm_type == AsmType::Float {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::XMM15),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::XMM15),
dst: dst.clone(),
});
} else {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
}
Instruction::Mov {
asm_type,
ref src,
ref dst,
} if is_large_imm(src) && is_memory_operand(dst) => {
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
Instruction::Mov {
ref src, ref dst, ..
} if src == dst => {
}
Instruction::Binary {
asm_type,
op,
ref src,
ref dst,
} if asm_type != AsmType::Double
&& asm_type != AsmType::Float
&& is_memory_operand(src)
&& is_memory_operand(dst) =>
{
if matches!(op, AsmBinaryOp::Mult) {
out.push(Instruction::Mov {
asm_type,
src: dst.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Binary {
asm_type,
op,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
} else {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Binary {
asm_type,
op,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
}
Instruction::Binary {
asm_type: AsmType::Quadword,
op,
src: Operand::Imm(v),
ref dst,
} if v > i32::MAX as i64 || v < i32::MIN as i64 => {
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Imm(v),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Binary {
asm_type: AsmType::Quadword,
op,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
Instruction::Binary {
asm_type,
op,
ref src,
ref dst,
} if (asm_type == AsmType::Double || asm_type == AsmType::Float)
&& is_memory_operand(dst) =>
{
out.push(Instruction::Mov {
asm_type,
src: dst.clone(),
dst: Operand::Register(Reg::XMM15),
});
out.push(Instruction::Binary {
asm_type,
op,
src: src.clone(),
dst: Operand::Register(Reg::XMM15),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::XMM15),
dst: dst.clone(),
});
}
Instruction::Binary {
asm_type,
op: AsmBinaryOp::Mult,
ref src,
ref dst,
} if asm_type != AsmType::Double
&& asm_type != AsmType::Float
&& is_memory_operand(dst) =>
{
out.push(Instruction::Mov {
asm_type,
src: dst.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Binary {
asm_type,
op: AsmBinaryOp::Mult,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::Cmp {
asm_type,
ref src,
ref dst,
} if asm_type != AsmType::Double
&& asm_type != AsmType::Float
&& is_memory_operand(src)
&& is_memory_operand(dst) =>
{
out.push(Instruction::Mov {
asm_type,
src: dst.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Cmp {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
}
Instruction::Cmp {
asm_type,
ref src,
dst: Operand::Imm(v),
} => {
out.push(Instruction::Mov {
asm_type,
src: Operand::Imm(v),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Cmp {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
}
Instruction::Cmp {
asm_type,
ref src,
ref dst,
} if is_large_imm(src) => {
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Cmp {
asm_type,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
Instruction::Idiv {
asm_type,
ref operand,
} if is_imm(operand) => {
out.push(Instruction::Mov {
asm_type,
src: operand.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Idiv {
asm_type,
operand: Operand::Register(Reg::R10),
});
}
Instruction::Div {
asm_type,
ref operand,
} if is_imm(operand) => {
out.push(Instruction::Mov {
asm_type,
src: operand.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Div {
asm_type,
operand: Operand::Register(Reg::R10),
});
}
Instruction::Movsx { ref src, ref dst } if is_imm(src) => {
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: src.clone(),
dst: dst.clone(),
});
}
Instruction::Movsx { ref src, ref dst } if is_memory_operand(dst) => {
out.push(Instruction::Movsx {
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::MovsxByte {
asm_type,
ref src,
ref dst,
} if is_imm(src) => {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: dst.clone(),
});
}
Instruction::MovsxByte {
asm_type,
ref src,
ref dst,
} if is_memory_operand(dst) => {
out.push(Instruction::MovsxByte {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::MovZeroExtend { ref src, ref dst } if is_imm(src) => {
out.push(Instruction::Mov {
asm_type: AsmType::Longword,
src: src.clone(),
dst: dst.clone(),
});
}
Instruction::MovZeroExtend { ref src, ref dst } if is_memory_operand(dst) => {
out.push(Instruction::MovZeroExtend {
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::MovZeroExtendByte {
asm_type,
ref src,
ref dst,
} if is_imm(src) => {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: dst.clone(),
});
}
Instruction::MovZeroExtendByte {
asm_type,
ref src,
ref dst,
} if is_memory_operand(dst) => {
out.push(Instruction::MovZeroExtendByte {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::MovsxWord {
asm_type,
ref src,
ref dst,
} if is_imm(src) => {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: dst.clone(),
});
}
Instruction::MovsxWord {
asm_type,
ref src,
ref dst,
} if is_memory_operand(dst) => {
out.push(Instruction::MovsxWord {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::MovZeroExtendWord {
asm_type,
ref src,
ref dst,
} if is_imm(src) => {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: dst.clone(),
});
}
Instruction::MovZeroExtendWord {
asm_type,
ref src,
ref dst,
} if is_memory_operand(dst) => {
out.push(Instruction::MovZeroExtendWord {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::Truncate { ref src, ref dst }
if is_memory_operand(src) && is_memory_operand(dst) =>
{
out.push(Instruction::Mov {
asm_type: AsmType::Longword,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Mov {
asm_type: AsmType::Longword,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
Instruction::Truncate { ref src, ref dst } if is_memory_operand(dst) => {
out.push(Instruction::Truncate {
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Mov {
asm_type: AsmType::Longword,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
Instruction::Lea { ref src, ref dst } if is_memory_operand(dst) => {
out.push(Instruction::Lea {
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::R10),
dst: dst.clone(),
});
}
Instruction::Cvtsi2sd {
asm_type,
ref src,
ref dst,
} if is_imm(src) || is_memory_operand(dst) => {
let fixed_src = if is_imm(src) {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
Operand::Register(Reg::R10)
} else {
src.clone()
};
if is_memory_operand(dst) {
out.push(Instruction::Cvtsi2sd {
asm_type,
src: fixed_src,
dst: Operand::Register(Reg::XMM15),
});
out.push(Instruction::Mov {
asm_type: AsmType::Double,
src: Operand::Register(Reg::XMM15),
dst: dst.clone(),
});
} else {
out.push(Instruction::Cvtsi2sd {
asm_type,
src: fixed_src,
dst: dst.clone(),
});
}
}
Instruction::Cvttsd2si {
asm_type,
ref src,
ref dst,
} if is_memory_operand(dst) => {
out.push(Instruction::Cvttsd2si {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::Cvtsi2ss {
asm_type,
ref src,
ref dst,
} if is_imm(src) || is_memory_operand(dst) => {
let fixed_src = if is_imm(src) {
out.push(Instruction::Mov {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
Operand::Register(Reg::R10)
} else {
src.clone()
};
if is_memory_operand(dst) {
out.push(Instruction::Cvtsi2ss {
asm_type,
src: fixed_src,
dst: Operand::Register(Reg::XMM15),
});
out.push(Instruction::Mov {
asm_type: AsmType::Float,
src: Operand::Register(Reg::XMM15),
dst: dst.clone(),
});
} else {
out.push(Instruction::Cvtsi2ss {
asm_type,
src: fixed_src,
dst: dst.clone(),
});
}
}
Instruction::Cvttss2si {
asm_type,
ref src,
ref dst,
} if is_memory_operand(dst) => {
out.push(Instruction::Cvttss2si {
asm_type,
src: src.clone(),
dst: Operand::Register(Reg::R11),
});
out.push(Instruction::Mov {
asm_type,
src: Operand::Register(Reg::R11),
dst: dst.clone(),
});
}
Instruction::Cvtss2sd { ref src, ref dst } if is_memory_operand(dst) => {
out.push(Instruction::Cvtss2sd {
src: src.clone(),
dst: Operand::Register(Reg::XMM15),
});
out.push(Instruction::Mov {
asm_type: AsmType::Double,
src: Operand::Register(Reg::XMM15),
dst: dst.clone(),
});
}
Instruction::Cvtsd2ss { ref src, ref dst } if is_memory_operand(dst) => {
out.push(Instruction::Cvtsd2ss {
src: src.clone(),
dst: Operand::Register(Reg::XMM15),
});
out.push(Instruction::Mov {
asm_type: AsmType::Float,
src: Operand::Register(Reg::XMM15),
dst: dst.clone(),
});
}
Instruction::JmpIndirect(ref op, ref targets) if is_memory_operand(op) => {
out.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: op.clone(),
dst: Operand::Register(Reg::R10),
});
out.push(Instruction::JmpIndirect(
Operand::Register(Reg::R10),
targets.clone(),
));
}
other => out.push(other),
}
}
fn insert_prologue_epilogue(
instructions: Vec<Instruction>,
spill_bytes: usize,
callee_saved_used: &[Reg],
) -> Vec<Instruction> {
let callee_count = callee_saved_used.len();
let callee_bytes = callee_count * 8;
let min_stack_offset = scan_min_stack_offset(&instructions);
let stack_bytes_from_scan = if min_stack_offset < 0 {
(-min_stack_offset) as usize
} else {
0
};
let needed_stack = std::cmp::max(spill_bytes, stack_bytes_from_scan);
let total = callee_bytes + needed_stack;
let aligned_total = (total + 15) & !15;
let alloc_size = aligned_total - callee_bytes;
let shift = -(callee_bytes as i32);
let instructions: Vec<Instruction> = if callee_bytes > 0 {
instructions
.into_iter()
.map(|instr| shift_stack_offsets(instr, shift))
.collect()
} else {
instructions
};
let mut result = Vec::new();
result.push(Instruction::Push(Operand::Register(Reg::BP)));
result.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::SP),
dst: Operand::Register(Reg::BP),
});
for ® in callee_saved_used {
result.push(Instruction::Push(Operand::Register(reg)));
}
if alloc_size > 0 {
result.push(Instruction::AllocateStack(alloc_size));
}
for instr in instructions {
if matches!(instr, Instruction::Ret) {
if alloc_size > 0 {
result.push(Instruction::DeallocateStack(alloc_size));
}
for ® in callee_saved_used.iter().rev() {
result.push(Instruction::Pop(Operand::Register(reg)));
}
result.push(Instruction::Pop(Operand::Register(Reg::BP)));
result.push(Instruction::Ret);
} else {
result.push(instr);
}
}
result
}
fn shift_stack_op(op: Operand, shift: i32) -> Operand {
match op {
Operand::Stack(offset) if offset <= 0 => Operand::Stack(offset + shift),
other => other,
}
}
fn shift_stack_offsets(instr: Instruction, shift: i32) -> Instruction {
match instr {
Instruction::Mov { asm_type, src, dst } => Instruction::Mov {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Unary {
asm_type,
op,
operand,
} => Instruction::Unary {
asm_type,
op,
operand: shift_stack_op(operand, shift),
},
Instruction::Cmp { asm_type, src, dst } => Instruction::Cmp {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::SetCC { condition, operand } => Instruction::SetCC {
condition,
operand: shift_stack_op(operand, shift),
},
Instruction::Binary {
asm_type,
op,
src,
dst,
} => Instruction::Binary {
asm_type,
op,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Idiv { asm_type, operand } => Instruction::Idiv {
asm_type,
operand: shift_stack_op(operand, shift),
},
Instruction::Div { asm_type, operand } => Instruction::Div {
asm_type,
operand: shift_stack_op(operand, shift),
},
Instruction::Movsx { src, dst } => Instruction::Movsx {
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::MovsxByte { asm_type, src, dst } => Instruction::MovsxByte {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::MovZeroExtend { src, dst } => Instruction::MovZeroExtend {
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::MovZeroExtendByte { asm_type, src, dst } => Instruction::MovZeroExtendByte {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::MovsxWord { asm_type, src, dst } => Instruction::MovsxWord {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::MovZeroExtendWord { asm_type, src, dst } => Instruction::MovZeroExtendWord {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Truncate { src, dst } => Instruction::Truncate {
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Push(op) => Instruction::Push(shift_stack_op(op, shift)),
Instruction::Pop(op) => Instruction::Pop(shift_stack_op(op, shift)),
Instruction::Cvtsi2sd { asm_type, src, dst } => Instruction::Cvtsi2sd {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Cvttsd2si { asm_type, src, dst } => Instruction::Cvttsd2si {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Cvtsi2ss { asm_type, src, dst } => Instruction::Cvtsi2ss {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Cvttss2si { asm_type, src, dst } => Instruction::Cvttss2si {
asm_type,
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Cvtss2sd { src, dst } => Instruction::Cvtss2sd {
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Cvtsd2ss { src, dst } => Instruction::Cvtsd2ss {
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::Lea { src, dst } => Instruction::Lea {
src: shift_stack_op(src, shift),
dst: shift_stack_op(dst, shift),
},
Instruction::JmpIndirect(op, targets) => {
Instruction::JmpIndirect(shift_stack_op(op, shift), targets)
}
Instruction::CallIndirect(op) => Instruction::CallIndirect(shift_stack_op(op, shift)),
instr @ (Instruction::SignExtend(_)
| Instruction::Jmp(_)
| Instruction::JmpCC(_, _)
| Instruction::Label(_)
| Instruction::AllocateStack(_)
| Instruction::DeallocateStack(_)
| Instruction::Call(_)
| Instruction::Ret
| Instruction::RawBytes(_)) => instr,
}
}
fn scan_min_stack_offset(instructions: &[Instruction]) -> i32 {
let mut min_offset: i32 = 0;
for instr in instructions {
for_each_operand(instr, |op| {
if let Operand::Stack(offset) = op
&& *offset < min_offset
{
min_offset = *offset;
}
});
}
min_offset
}
fn for_each_operand<F: FnMut(&Operand)>(instr: &Instruction, mut f: F) {
match instr {
Instruction::Mov { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::Unary { operand, .. } => {
f(operand);
}
Instruction::Cmp { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::SetCC { operand, .. } => {
f(operand);
}
Instruction::Binary { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::Idiv { operand, .. } => {
f(operand);
}
Instruction::Div { operand, .. } => {
f(operand);
}
Instruction::Movsx { src, dst } => {
f(src);
f(dst);
}
Instruction::MovsxByte { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::MovZeroExtend { src, dst } => {
f(src);
f(dst);
}
Instruction::MovZeroExtendByte { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::MovsxWord { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::MovZeroExtendWord { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::Truncate { src, dst } => {
f(src);
f(dst);
}
Instruction::Push(op) | Instruction::Pop(op) => {
f(op);
}
Instruction::Cvtsi2sd { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::Cvttsd2si { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::Cvtsi2ss { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::Cvttss2si { src, dst, .. } => {
f(src);
f(dst);
}
Instruction::Cvtss2sd { src, dst } => {
f(src);
f(dst);
}
Instruction::Cvtsd2ss { src, dst } => {
f(src);
f(dst);
}
Instruction::Lea { src, dst } => {
f(src);
f(dst);
}
Instruction::JmpIndirect(op, _) => {
f(op);
}
Instruction::CallIndirect(op) => {
f(op);
}
Instruction::SignExtend(_)
| Instruction::Jmp(_)
| Instruction::JmpCC(_, _)
| Instruction::Label(_)
| Instruction::AllocateStack(_)
| Instruction::DeallocateStack(_)
| Instruction::Call(_)
| Instruction::Ret
| Instruction::RawBytes(_) => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merge_nodes_keeps_adjacency_symmetric() {
let mut graph = InterferenceGraph::new();
let di = GraphNode::HardReg(Reg::DI);
let x = GraphNode::Pseudo("x".into());
let tmp = GraphNode::Pseudo("tmp".into());
graph.add_node(x.clone());
graph.add_node(tmp.clone());
graph.add_edge(&tmp, &x);
assert!(graph.adj.get(&tmp).unwrap().contains(&x));
assert!(graph.adj.get(&x).unwrap().contains(&tmp));
assert!(
!graph.adj.contains_key(&di),
"DI should have no adj entry before merge"
);
merge_nodes(&mut graph, &x, &di);
assert!(
graph.adj.get(&di).is_some_and(|s| s.contains(&tmp)),
"adj[DI] must contain tmp after merging x→DI"
);
assert!(
graph.adj.get(&tmp).is_some_and(|s| s.contains(&di)),
"adj[tmp] must contain DI after merging x→DI"
);
assert!(
!graph.adj.contains_key(&x),
"adj[x] should be removed after merge"
);
assert!(
!graph.adj.get(&tmp).unwrap().contains(&x),
"adj[tmp] should no longer contain x after merge"
);
}
#[test]
fn merge_nodes_symmetric_with_existing_edges() {
let mut graph = InterferenceGraph::new();
let di = GraphNode::HardReg(Reg::DI);
let x = GraphNode::Pseudo("x".into());
let tmp = GraphNode::Pseudo("tmp".into());
let count = GraphNode::Pseudo("count".into());
graph.add_node(x.clone());
graph.add_node(tmp.clone());
graph.add_node(count.clone());
graph.add_edge(&tmp, &x);
graph.add_edge(&count, &x);
graph.add_edge(&count, &di);
merge_nodes(&mut graph, &x, &di);
assert!(graph.adj.get(&di).unwrap().contains(&tmp));
assert!(graph.adj.get(&tmp).unwrap().contains(&di));
assert!(graph.adj.get(&di).unwrap().contains(&count));
assert!(graph.adj.get(&count).unwrap().contains(&di));
assert!(!graph.adj.get(&di).unwrap().contains(&di));
}
}