use alloc::vec;
use alloc::vec::Vec;
use super::color::is_remat;
use super::liveness::Analysis;
use super::{Location, RegAllocError};
use crate::mir::cfg::Cfg;
use crate::mir::visit::{Access, RegPos};
use crate::mir::{Block, Effects, Function, Inst, MirError, Operand, StackSlot};
use crate::regs::{Reg, VReg};
use crate::target::{InstKind, Opcode, OperandRule};
pub(crate) struct SpillState {
unspillable: Vec<bool>,
pub(crate) spilled: u32,
pub(crate) rematerialized: u32,
}
impl SpillState {
pub(crate) fn new(f: &Function) -> SpillState {
let mut state = SpillState {
unspillable: vec![false; f.vreg_count()],
spilled: 0,
rematerialized: 0,
};
for b in f.blocks() {
if let Some(&last) = f.insts(b).last() {
if kind_of(f, last).is_terminator() {
f.visit_regs(last, |_, r, access| {
if let (Access::Def, Some(v)) = (access, r.as_virt()) {
state.pin(v.index() as u32);
}
});
}
}
}
state
}
pub(crate) fn spillable(&self, v: u32) -> bool {
!self.unspillable.get(v as usize).copied().unwrap_or(true)
}
fn pin(&mut self, v: u32) {
let i = v as usize;
if i >= self.unspillable.len() {
self.unspillable.resize(i + 1, false);
}
self.unspillable[i] = true;
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Def {
None,
Param { block: Block, index: usize },
Inst(Inst),
}
pub(crate) struct UseIndex {
pub(crate) def: Vec<Def>,
start: Vec<u32>,
uses: Vec<(Inst, RegPos)>,
dead: Vec<bool>,
}
impl UseIndex {
pub(crate) fn new(f: &Function, cfg: &Cfg) -> Result<UseIndex, MirError> {
let nv = f.vreg_count();
let mut def = vec![Def::None; nv];
let mut raw: Vec<(u32, Inst, RegPos)> = Vec::with_capacity(f.inst_count() * 2);
let mut count = vec![0u32; nv + 1];
let mut defs_here: Vec<u32> = Vec::new();
for &b in &cfg.rpo {
let block = Block::from_index(b as usize);
for (index, p) in f.params(block).iter().enumerate() {
if let Some(v) = p.reg().and_then(Reg::as_virt) {
if let Some(d) = def.get_mut(v.index()) {
*d = Def::Param { block, index };
}
}
}
for &inst in f.insts(block) {
let mut err = None;
defs_here.clear();
f.visit_regs(inst, |pos, r, access| {
let Some(v) = r.as_virt() else { return };
match access {
Access::Def => defs_here.push(v.index() as u32),
Access::Use => {
let dominated = match def.get(v.index()) {
Some(Def::Param { block: d, .. }) => {
cfg.dominates(d.index(), b as usize)
}
Some(Def::Inst(d)) => f
.inst_block(*d)
.is_some_and(|db| cfg.dominates(db.index(), b as usize)),
_ => false,
};
if !dominated && err.is_none() {
err = Some(v);
}
if let Some(c) = count.get_mut(v.index() + 1) {
*c += 1;
raw.push((v.index() as u32, inst, pos));
}
}
}
});
if let Some(v) = err {
return Err(undominated(f, v, inst));
}
for &v in &defs_here {
if let Some(d) = def.get_mut(v as usize) {
*d = Def::Inst(inst);
}
}
}
}
for k in 0..nv {
count[k + 1] += count[k];
}
let mut fill = count.clone();
let mut uses = vec![(Inst::new(0), RegPos::Op(0)); raw.len()];
for (v, inst, pos) in raw {
let at = &mut fill[v as usize];
uses[*at as usize] = (inst, pos);
*at += 1;
}
Ok(UseIndex {
def,
start: count,
uses,
dead: vec![false; f.inst_count()],
})
}
pub(crate) fn uses(&self, v: usize) -> &[(Inst, RegPos)] {
match (self.start.get(v), self.start.get(v + 1)) {
(Some(&s), Some(&e)) => self.uses.get(s as usize..e as usize).unwrap_or(&[]),
_ => &[],
}
}
pub(crate) fn is_dead(&self, inst: Inst) -> bool {
self.dead.get(inst.index()).copied().unwrap_or(false)
}
fn kill(&mut self, inst: Inst) {
if let Some(d) = self.dead.get_mut(inst.index()) {
*d = true;
}
}
}
fn undominated(f: &Function, v: VReg, inst: Inst) -> MirError {
let mut defined = false;
for b in f.blocks() {
if f.params(b)
.iter()
.any(|p| p.reg().and_then(Reg::as_virt) == Some(v))
{
defined = true;
break;
}
for &i in f.insts(b) {
f.visit_regs(i, |_, r, access| {
if access == Access::Def && r.as_virt() == Some(v) {
defined = true;
}
});
}
if defined {
break;
}
}
if defined {
MirError::NotDominated { vreg: v, inst }
} else {
MirError::UndefinedVReg { vreg: v }
}
}
#[derive(Default)]
struct Edits {
insert: Vec<(u32, u8, u32, Inst)>,
deleted: Vec<Inst>,
}
impl Edits {
fn before(&mut self, anchor: Inst, new: Inst) {
let seq = self.insert.len() as u32;
self.insert.push((anchor.index() as u32, 0, seq, new));
}
fn after(&mut self, anchor: Inst, new: Inst) {
let seq = self.insert.len() as u32;
self.insert.push((anchor.index() as u32, 1, seq, new));
}
fn delete(&mut self, inst: Inst) {
self.deleted.push(inst);
}
fn apply(mut self, f: &mut Function, gone: impl Fn(Inst) -> bool) {
if self.insert.is_empty() && self.deleted.is_empty() {
return;
}
self.insert.sort_unstable();
let mut first = vec![u32::MAX; f.inst_count()];
for (k, e) in self.insert.iter().enumerate().rev() {
if let Some(x) = first.get_mut(e.0 as usize) {
*x = k as u32;
}
}
let mut blocks: Vec<u32> = self
.insert
.iter()
.map(|e| Inst::new(e.0))
.chain(self.deleted.iter().copied())
.filter_map(|i| f.inst_block(i))
.map(|b| b.index() as u32)
.collect();
blocks.sort_unstable();
blocks.dedup();
for b in blocks {
let block = Block::from_index(b as usize);
let mut list = f.take_block_insts(block);
let has_inserts = list
.iter()
.any(|i| first.get(i.index()).is_some_and(|&k| k != u32::MAX));
if !has_inserts {
list.retain(|&i| !gone(i));
f.set_block_insts(block, list);
continue;
}
let mut out = Vec::with_capacity(list.len() + 4);
for inst in list {
let id = inst.index() as u32;
let mut j = first.get(inst.index()).map_or(usize::MAX, |&k| k as usize);
while let Some(&(a, place, _, new)) = self.insert.get(j) {
if a != id || place != 0 {
break;
}
out.push(new);
j += 1;
}
if !gone(inst) {
out.push(inst);
}
while let Some(&(a, _, _, new)) = self.insert.get(j) {
if a != id {
break;
}
out.push(new);
j += 1;
}
}
f.set_block_insts(block, out);
}
}
}
pub(crate) fn remove_dead_pure(f: &mut Function, index: &mut UseIndex) {
let nv = f.vreg_count();
let mut uses: Vec<u32> = (0..nv).map(|v| index.uses(v).len() as u32).collect();
let removable = |f: &Function, inst: Inst| -> bool {
let copy = kind_of(f, inst) == InstKind::Copy
&& f.operands(inst)
.first()
.and_then(Operand::reg)
.is_some_and(Reg::is_virtual);
copy || is_remat(f, inst)
};
let mut work: Vec<u32> = (0..nv as u32).filter(|&v| uses[v as usize] == 0).collect();
let mut edits = Edits::default();
while let Some(v) = work.pop() {
let Some(Def::Inst(inst)) = index.def.get(v as usize).copied() else {
continue;
};
if index.is_dead(inst) || !removable(f, inst) {
continue;
}
index.kill(inst);
edits.delete(inst);
f.visit_regs(inst, |_, r, access| {
if let (Access::Use, Some(u)) = (access, r.as_virt()) {
if let Some(n) = uses.get_mut(u.index()) {
*n = n.saturating_sub(1);
if *n == 0 {
work.push(u.index() as u32);
}
}
}
});
}
edits.apply(f, |i| index.is_dead(i));
}
fn rule_at(f: &Function, inst: Inst, k: usize) -> Option<OperandRule> {
let desc = f.opcode(inst).and_then(|o| f.target().inst(o))?;
match desc.kind() {
InstKind::Copy | InstKind::ParallelCopy => None,
_ => desc.operands().get(k).copied(),
}
}
pub(crate) fn kind_of(f: &Function, inst: Inst) -> InstKind {
f.opcode(inst)
.and_then(|o| f.target().inst(o))
.map_or(InstKind::Plain, |d| d.kind())
}
#[derive(Default)]
pub(crate) struct SpillReport {
pub(crate) spilled: Vec<u32>,
pub(crate) stored: Vec<(u32, Inst, Inst)>,
pub(crate) temps: Vec<(u32, Inst, Inst)>,
pub(crate) anchors: Vec<Inst>,
}
pub(crate) fn spill(
f: &mut Function,
index: &mut UseIndex,
state: &mut SpillState,
values: &[VReg],
) -> Result<SpillReport, RegAllocError> {
#[derive(Clone, Copy)]
enum How {
Slot(StackSlot),
Remat(Inst),
}
let mut report = SpillReport::default();
let mut plan: Vec<(VReg, How)> = Vec::with_capacity(values.len());
let mut edits = Edits::default();
for &v in values {
let i = v.index();
if !state.spillable(i as u32) {
continue;
}
let class = super::class_of(f, v);
let how = match index.def.get(i).copied().unwrap_or(Def::None) {
Def::None => continue,
Def::Inst(inst) if is_remat(f, inst) => {
index.kill(inst);
edits.delete(inst);
state.rematerialized += 1;
How::Remat(inst)
}
Def::Param { block, index: k } => {
let s = f.spill_slot(class)?;
let mut params = f.params(block).to_vec();
if let Some(p) = params.get_mut(k) {
*p = Operand::slot(s);
}
f.set_params(block, params);
How::Slot(s)
}
Def::Inst(inst) => {
let s = f.spill_slot(class)?;
match kind_of(f, inst) {
InstKind::Copy | InstKind::ParallelCopy => {
let mut at = None;
f.visit_regs(inst, |pos, r, access| {
if access == Access::Def && r.as_virt() == Some(v) {
at = Some(pos);
}
});
if let Some(pos) = at {
f.set_operand_at(pos, Operand::slot(s));
}
}
_ => {
let block = f.inst_block(inst).ok_or(MirError::Limit)?;
let store = f.create_inst(
block,
Opcode::COPY,
&[Operand::slot(s), Reg::from(v).into()],
Effects::new(),
)?;
edits.after(inst, store);
report.stored.push((i as u32, inst, store));
}
}
How::Slot(s)
}
};
state.spilled += 1;
if !matches!(how, How::Remat(_)) {
state.pin(i as u32);
}
report.spilled.push(i as u32);
plan.push((v, how));
}
for &(v, how) in &plan {
let mut last: Option<(Inst, Reg)> = None;
let index: &UseIndex = index;
for &(inst, pos) in index.uses(v.index()) {
if index.is_dead(inst) {
continue;
}
let kind = kind_of(f, inst);
let base = f.operand_base(inst);
if let Some((li, t)) = last {
if li == inst {
f.set_reg_at(pos, t);
continue;
}
}
let make: (Opcode, Operand) = match how {
How::Remat(def) => {
let opcode = f.opcode(def).ok_or(MirError::Limit)?;
let src = f.operands(def).get(1).copied().ok_or(MirError::Limit)?;
if let Operand::Imm(imm) = src {
let value = if opcode == crate::x86_64::op::MOV32RI {
i64::from(imm as u32)
} else {
imm
};
if imm_ok(f, inst, kind, pos, base, value) {
f.set_operand_at(pos, Operand::Imm(value));
continue;
}
}
(opcode, src)
}
How::Slot(s) => {
if slot_ok(f, inst, kind, pos, base) {
f.set_operand_at(pos, Operand::slot(s));
continue;
}
(Opcode::COPY, Operand::slot(s))
}
};
let class = super::class_of(f, v);
let t = f.vreg(class)?;
if let Some(tv) = t.as_virt() {
state.pin(tv.index() as u32);
let block = f.inst_block(inst).ok_or(MirError::Limit)?;
let load = f.create_inst(block, make.0, &[t.into(), make.1], Effects::new())?;
edits.before(inst, load);
report.temps.push((tv.index() as u32, load, inst));
report.anchors.push(inst);
}
f.set_reg_at(pos, t);
last = Some((inst, t));
}
}
report.anchors.sort_unstable();
report.anchors.dedup();
edits.apply(f, |i| index.is_dead(i));
Ok(report)
}
fn imm_ok(f: &Function, inst: Inst, kind: InstKind, pos: RegPos, base: usize, value: i64) -> bool {
match pos {
RegPos::Arg(_) => true,
RegPos::Op(i) => {
let k = (i as usize).saturating_sub(base);
match kind {
InstKind::Copy | InstKind::ParallelCopy => true,
_ => {
matches!(rule_at(f, inst, k), Some(OperandRule::Use(_, acc)) if acc.imm().fits(value))
}
}
}
RegPos::MemBase(_) | RegPos::MemIndex(_) => false,
}
}
fn slot_ok(f: &Function, inst: Inst, kind: InstKind, pos: RegPos, base: usize) -> bool {
match pos {
RegPos::Arg(_) => true,
RegPos::Op(i) => {
let k = (i as usize).saturating_sub(base);
match kind {
InstKind::Copy | InstKind::ParallelCopy => true,
_ => {
let mems = f.operands(inst).iter().filter(|o| o.is_mem()).count();
mems < usize::from(f.target().max_mem_operands())
&& matches!(rule_at(f, inst, k), Some(OperandRule::Use(_, acc)) if acc.mem())
}
}
}
RegPos::MemBase(_) | RegPos::MemIndex(_) => false,
}
}
pub(crate) fn spill_all(
f: &mut Function,
index: &mut UseIndex,
state: &mut SpillState,
) -> Result<SpillReport, RegAllocError> {
let all: Vec<VReg> = (0..index.def.len())
.filter(|&i| state.spillable(i as u32) && !matches!(index.def[i], Def::None))
.map(|i| VReg::from_index(i as u32))
.collect();
spill(f, index, state, &all)
}
pub(crate) fn apply(
f: &mut Function,
an: &Analysis<'_>,
locations: &[Location],
) -> Result<(), RegAllocError> {
let mut edits = Edits::default();
let mut gone = vec![false; f.inst_count()];
let place = |v: VReg| -> Result<Reg, RegAllocError> {
match locations.get(v.index()) {
Some(Location::Reg(r)) => Ok(Reg::from(*r)),
_ => Err(RegAllocError::Mir(MirError::UnknownVReg(v))),
}
};
for b in 0..f.block_count() {
let block = Block::from_index(b);
if !an.cfg.reachable(b) {
let trap = f.create_inst(block, crate::x86_64::op::UD2, &[], Effects::new())?;
f.set_block_insts(block, vec![trap]);
f.set_params(block, Vec::new());
continue;
}
if f.params(block)
.iter()
.any(|p| p.reg().is_some_and(Reg::is_virtual))
{
let mut params = f.params(block).to_vec();
for p in &mut params {
if let Some(v) = p.reg().and_then(Reg::as_virt) {
*p = Operand::Reg(place(v)?);
}
}
f.set_params(block, params);
}
let count = f.insts(block).len();
for k in 0..count {
let Some(&inst) = f.insts(block).get(k) else {
break;
};
f.map_vregs(inst, place)?;
let Some(desc) = f.opcode(inst).and_then(|o| f.target().inst(o)) else {
continue;
};
match desc.kind() {
InstKind::Copy => {
let ops = f.operands(inst);
if ops.first() == ops.get(1) {
if let Some(g) = gone.get_mut(inst.index()) {
*g = true;
}
edits.delete(inst);
}
}
InstKind::ParallelCopy => {}
_ => {
let base = f.operand_base(inst);
for (k, rule) in desc.operands().iter().enumerate() {
if let OperandRule::Tied(d) = *rule {
let ops = f.operands(inst);
let (Some(def), Some(used)) =
(ops.get(usize::from(d)).copied(), ops.get(k).copied())
else {
continue;
};
if def != used {
let copy = f.create_inst(
block,
Opcode::COPY,
&[def, used],
Effects::new(),
)?;
edits.before(inst, copy);
if let Some(r) = def.reg() {
f.set_reg_at(RegPos::Op((base + k) as u32), r);
}
}
}
}
}
}
}
}
edits.apply(f, |i| gone.get(i.index()).copied().unwrap_or(false));
Ok(())
}