use std::collections::HashMap;
use rucc_base::{Interner, Symbol};
use rucc_mir::{self as mir, Role};
use rucc_target::MachineInsts;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Plan {
pub opcode: mir::Opcode,
pub operands: Vec<mir::Operand>,
pub imm: Option<i64>,
pub amode: Option<mir::Amode>,
pub symbol: Option<Symbol>,
}
impl Plan {
#[must_use]
pub fn of(func: &mir::Func, inst: mir::Inst) -> Self {
let data = &func[inst];
Self {
opcode: data.opcode,
operands: func[data.operands].to_vec(),
imm: data.imm.map(|at| func[at].0),
amode: data.mem.map(|at| func[at]),
symbol: data.symbol,
}
}
fn reads(&self) -> impl Iterator<Item = mir::Reg> + use<'_> {
self.operands.iter().filter(|operand| operand.role == Role::Use).map(|operand| operand.reg)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Refusal {
Twice(mir::Inst),
Gone(mir::Inst),
Unknown(mir::Inst),
Operands(mir::Inst),
Imm(mir::Inst),
Mem(mir::Inst),
Scale(mir::Inst),
Read(mir::Inst),
Class(mir::Inst),
Edge(mir::Block, usize),
Args(mir::Block, usize),
}
#[derive(Debug, Clone, Default)]
pub struct Reads {
counts: HashMap<mir::Reg, usize>,
}
impl Reads {
#[must_use]
pub fn of(func: &mir::Func) -> Self {
let mut counts: HashMap<mir::Reg, usize> = HashMap::new();
for block in func.blocks() {
for inst in func.insts(block) {
for operand in &func[func[inst].operands] {
if operand.role == Role::Use {
*counts.entry(operand.reg).or_insert(0) += 1;
}
}
}
for call in &func[block].succs {
for &arg in &call.args {
*counts.entry(arg).or_insert(0) += 1;
}
}
}
Self { counts }
}
#[must_use]
pub fn count(&self, reg: mir::Reg) -> usize {
self.counts.get(®).copied().unwrap_or(0)
}
fn gained(&mut self, reg: mir::Reg) {
*self.counts.entry(reg).or_insert(0) += 1;
}
fn lost(&mut self, reg: mir::Reg) {
if let Some(count) = self.counts.get_mut(®) {
*count = count.saturating_sub(1);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum What {
Rewrite(Plan),
Rename { from: mir::Reg, into: mir::Reg },
Remove,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Carried {
from: mir::Block,
at: usize,
args: Vec<mir::Reg>,
}
#[derive(Debug, Clone, Default)]
pub struct Changes {
changes: Vec<(mir::Inst, What)>,
carries: Vec<Carried>,
}
impl Changes {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.changes.len() + self.carries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.changes.is_empty() && self.carries.is_empty()
}
pub fn rewrite(&mut self, inst: mir::Inst, plan: Plan) {
self.changes.push((inst, What::Rewrite(plan)));
}
pub fn rename(&mut self, inst: mir::Inst, from: mir::Reg, into: mir::Reg) {
self.changes.push((inst, What::Rename { from, into }));
}
pub fn carry(&mut self, from: mir::Block, at: usize, args: Vec<mir::Reg>) {
self.carries.push(Carried { from, at, args });
}
pub fn remove(&mut self, inst: mir::Inst) {
self.changes.push((inst, What::Remove));
}
#[must_use]
pub fn refused(
&self,
func: &mir::Func,
reads: &Reads,
names: &Interner,
machine: &MachineInsts,
) -> Option<Refusal> {
for (at, &(inst, _)) in self.changes.iter().enumerate() {
if self.changes[..at].iter().any(|&(other, _)| other == inst) {
return Some(Refusal::Twice(inst));
}
if func.block_of(inst).is_none() {
return Some(Refusal::Gone(inst));
}
}
for (at, carried) in self.carries.iter().enumerate() {
let edge = (carried.from, carried.at);
if self.carries[..at].iter().any(|other| (other.from, other.at) == edge) {
return Some(Refusal::Edge(carried.from, carried.at));
}
let Some(call) = func[carried.from].succs.get(carried.at) else {
return Some(Refusal::Edge(carried.from, carried.at));
};
if func[call.block].params.len() != carried.args.len() {
return Some(Refusal::Args(carried.from, carried.at));
}
}
for &(inst, ref what) in &self.changes {
match what {
What::Rewrite(plan) => {
if let Some(refusal) = shaped(inst, plan, names, machine) {
return Some(refusal);
}
}
What::Rename { from, into } => {
if let Some(refusal) = renamed(func, inst, *from, *into) {
return Some(refusal);
}
}
What::Remove => {
if self.read_after(func, reads, inst) {
return Some(Refusal::Read(inst));
}
}
}
}
None
}
pub fn commit(
self,
func: &mut mir::Func,
reads: &mut Reads,
names: &Interner,
machine: &MachineInsts,
) -> Result<usize, Refusal> {
if let Some(refusal) = self.refused(func, reads, names, machine) {
return Err(refusal);
}
let touched = self.len();
for (inst, what) in self.changes {
let (lost, gained) = moved(func, inst, &what);
for reg in lost {
reads.lost(reg);
}
for reg in gained {
reads.gained(reg);
}
match what {
What::Rewrite(plan) => {
let operands = func.push_operands(&plan.operands);
let imm = plan.imm.map(|value| func.add_imm(value));
let mem = plan.amode.map(|amode| func.add_amode(amode));
let data = &mut func[inst];
data.opcode = plan.opcode;
data.operands = operands;
data.imm = imm;
data.mem = mem;
data.symbol = plan.symbol;
}
What::Rename { from, into } => {
let operands = func[inst].operands;
for operand in &mut func[operands] {
if operand.role == Role::Use && operand.reg == from {
operand.reg = into;
}
}
}
What::Remove => func.remove_inst(inst),
}
}
for carried in self.carries {
for &arg in &func[carried.from].succs[carried.at].args {
reads.lost(arg);
}
for &arg in &carried.args {
reads.gained(arg);
}
func.succs_mut(carried.from)[carried.at].args = carried.args;
}
Ok(touched)
}
fn read_after(&self, func: &mir::Func, reads: &Reads, inst: mir::Inst) -> bool {
func[func[inst].operands].iter().filter(|operand| operand.role.is_def()).any(|operand| {
let mut left = reads.count(operand.reg);
let mut settle = |lost: &[mir::Reg], gained: &[mir::Reg]| {
let goes = lost.iter().filter(|&®| reg == operand.reg).count();
left = left.saturating_sub(goes);
left += gained.iter().filter(|&®| reg == operand.reg).count();
};
for &(other, ref what) in &self.changes {
let (lost, gained) = moved(func, other, what);
settle(&lost, &gained);
}
for carried in &self.carries {
settle(&func[carried.from].succs[carried.at].args, &carried.args);
}
left != 0
})
}
}
fn moved(func: &mir::Func, inst: mir::Inst, what: &What) -> (Vec<mir::Reg>, Vec<mir::Reg>) {
let held: Vec<mir::Reg> = func[func[inst].operands]
.iter()
.filter(|operand| operand.role == Role::Use)
.map(|operand| operand.reg)
.collect();
match what {
What::Rewrite(plan) => (held, plan.reads().collect()),
What::Rename { from, into } => {
let gone: Vec<mir::Reg> = held.into_iter().filter(|reg| reg == from).collect();
let back = vec![*into; gone.len()];
(gone, back)
}
What::Remove => (held, Vec::new()),
}
}
fn renamed(func: &mir::Func, inst: mir::Inst, from: mir::Reg, into: mir::Reg) -> Option<Refusal> {
let class = func.class_of(into)?;
func[func[inst].operands]
.iter()
.any(|operand| operand.role == Role::Use && operand.reg == from && operand.class != class)
.then_some(Refusal::Class(inst))
}
fn shaped(
inst: mir::Inst,
plan: &Plan,
names: &Interner,
machine: &MachineInsts,
) -> Option<Refusal> {
let name = names.resolve(plan.opcode.name());
let bare = machine.bare(name);
let Some(desc) = (machine.operands)(bare) else { return Some(Refusal::Unknown(inst)) };
if plan.operands.len() < desc.len() {
return Some(Refusal::Operands(inst));
}
let (described, addressed) = plan.operands.split_at(desc.len());
for (operand, want) in described.iter().zip(desc) {
let shape = (operand.class, operand.role, operand.constraint);
if shape != (want.class, want.role, want.constraint) {
return Some(Refusal::Operands(inst));
}
}
if plan.imm.is_some() != (machine.takes_imm)(bare) {
return Some(Refusal::Imm(inst));
}
let Some(amode) = plan.amode else {
return ((machine.takes_mem)(bare) || !addressed.is_empty()).then_some(Refusal::Mem(inst));
};
if !(machine.takes_mem)(bare) {
return Some(Refusal::Mem(inst));
}
let named = [amode.base, amode.index].into_iter().flatten();
let mut wanted = 0;
for at in named {
let Some(operand) = plan.operands.get(usize::from(at)) else {
return Some(Refusal::Operands(inst));
};
if usize::from(at) < desc.len() || operand.role != Role::Use {
return Some(Refusal::Operands(inst));
}
wanted += 1;
}
if addressed.len() != wanted {
return Some(Refusal::Operands(inst));
}
let scaled = if amode.index.is_some() { amode.scale } else { 1 };
if !machine.scales(scaled) || (amode.index.is_none() && amode.scale != 1) {
return Some(Refusal::Scale(inst));
}
None
}
#[cfg(test)]
mod tests {
use rucc_mir::Constraint;
use rucc_target::x86_64::{GPR, MACHINE, XMM};
use super::*;
fn empty() -> (Interner, mir::Func, mir::Block) {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
let block = func.create_block();
(names, func, block)
}
fn op(names: &mut Interner, name: &str) -> mir::Opcode {
mir::Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
}
fn refused(func: &mir::Func, names: &Interner, set: &Changes) -> Option<Refusal> {
set.refused(func, &Reads::of(func), names, &MACHINE)
}
fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<String> {
func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
}
fn copy(
func: &mut mir::Func,
names: &mut Interner,
block: mir::Block,
) -> (mir::Inst, mir::Reg) {
let from = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let mov = op(names, "mov_rr_64");
(func.build(block, mov).def(into, GPR).uses(from, GPR).finish(), into)
}
#[test]
fn a_rewrite_the_target_has_is_taken() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let base = func.new_vreg(GPR);
let load = op(&mut names, "mov_rm_64");
let mut set = Changes::new();
set.rewrite(
mov,
Plan {
opcode: load,
operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(base, GPR)],
imm: None,
amode: Some(mir::Amode {
base: Some(1),
index: None,
scale: 1,
disp: 8,
symbol: None,
block: None,
reach: mir::Reach::Itself,
segment: None,
}),
symbol: None,
},
);
let mut reads = Reads::of(&func);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
assert_eq!(func[func[mov].mem.expect("the load has an address")].disp, 8);
assert_eq!(reads.count(base), 1, "the address register is read now");
assert_eq!(reads.count(into), 0, "the register the copy read is read by nothing");
}
#[test]
fn an_opcode_this_target_does_not_have_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let made_up = op(&mut names, "mov_rr_65");
let mut set = Changes::new();
set.rewrite(
mov,
Plan {
opcode: made_up,
operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
imm: None,
amode: None,
symbol: None,
},
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Unknown(mov)));
assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"], "the function was written to");
}
#[test]
fn an_operand_of_the_wrong_class_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let float = func.new_vreg(XMM);
let same = func[mov].opcode;
let mut set = Changes::new();
set.rewrite(
mov,
Plan {
opcode: same,
operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(float, XMM)],
imm: None,
amode: None,
symbol: None,
},
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
}
#[test]
fn an_add_whose_answer_is_not_tied_to_its_source_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let other = func.new_vreg(GPR);
let add = op(&mut names, "add_rr_64");
let loose = vec![
mir::Operand::write(into, GPR),
mir::Operand::read(into, GPR),
mir::Operand::read(other, GPR),
];
let mut set = Changes::new();
set.rewrite(
mov,
Plan { opcode: add, operands: loose.clone(), imm: None, amode: None, symbol: None },
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
let mut tied = loose;
tied[0] = tied[0].with(Constraint::Reuse(1));
let mut set = Changes::new();
set.rewrite(
mov,
Plan { opcode: add, operands: tied, imm: None, amode: None, symbol: None },
);
assert_eq!(refused(&func, &names, &set), None, "the same instruction written properly");
}
#[test]
fn an_immediate_has_to_be_there_exactly_when_the_instruction_carries_one() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let same = func[mov].opcode;
let add = op(&mut names, "add_ri_64");
let mut set = Changes::new();
set.rewrite(
mov,
Plan {
opcode: same,
operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
imm: Some(7),
amode: None,
symbol: None,
},
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Imm(mov)), "a copy of seven");
let tied = vec![
mir::Operand::write(into, GPR).with(Constraint::Reuse(1)),
mir::Operand::read(into, GPR),
];
let mut set = Changes::new();
set.rewrite(
mov,
Plan { opcode: add, operands: tied.clone(), imm: None, amode: None, symbol: None },
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Imm(mov)), "an add of nothing");
let mut set = Changes::new();
set.rewrite(
mov,
Plan { opcode: add, operands: tied, imm: Some(7), amode: None, symbol: None },
);
assert_eq!(refused(&func, &names, &set), None);
}
#[test]
fn an_address_on_an_instruction_that_has_none_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let base = func.new_vreg(GPR);
let same = func[mov].opcode;
let mut set = Changes::new();
set.rewrite(
mov,
Plan {
opcode: same,
operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(base, GPR)],
imm: None,
amode: Some(mir::Amode {
base: Some(1),
index: None,
scale: 1,
disp: 0,
symbol: None,
block: None,
reach: mir::Reach::Itself,
segment: None,
}),
symbol: None,
},
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Mem(mov)));
}
#[test]
fn an_instruction_that_wants_an_address_and_has_none_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let load = op(&mut names, "mov_rm_64");
let mut set = Changes::new();
set.rewrite(
mov,
Plan {
opcode: load,
operands: vec![mir::Operand::write(into, GPR)],
imm: None,
amode: None,
symbol: None,
},
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Mem(mov)));
}
#[test]
fn an_index_scaled_by_something_this_machine_cannot_write_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let base = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let load = op(&mut names, "mov_rm_64");
let scaled = |scale| Plan {
opcode: load,
operands: vec![
mir::Operand::write(into, GPR),
mir::Operand::read(base, GPR),
mir::Operand::read(index, GPR),
],
imm: None,
amode: Some(mir::Amode {
base: Some(1),
index: Some(2),
scale,
disp: 0,
symbol: None,
block: None,
reach: mir::Reach::Itself,
segment: None,
}),
symbol: None,
};
let mut set = Changes::new();
set.rewrite(mov, scaled(3));
assert_eq!(refused(&func, &names, &set), Some(Refusal::Scale(mov)));
let mut set = Changes::new();
set.rewrite(mov, scaled(4));
assert_eq!(refused(&func, &names, &set), None);
}
#[test]
fn an_address_pointing_at_an_operand_of_its_own_instruction_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let load = op(&mut names, "mov_rm_64");
let mut set = Changes::new();
set.rewrite(
mov,
Plan {
opcode: load,
operands: vec![mir::Operand::write(into, GPR)],
imm: None,
amode: Some(mir::Amode {
base: Some(0),
index: None,
scale: 1,
disp: 0,
symbol: None,
block: None,
reach: mir::Reach::Itself,
segment: None,
}),
symbol: None,
},
);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
}
#[test]
fn removing_an_instruction_whose_answer_is_still_read_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, into) = copy(&mut func, &mut names, block);
let out = func.new_vreg(GPR);
let second = func[mov].opcode;
func.build(block, second).def(out, GPR).uses(into, GPR).finish();
let mut set = Changes::new();
set.remove(mov);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(mov)));
assert_eq!(shape(&func, &names, block).len(), 2);
}
#[test]
fn removing_it_in_a_set_that_takes_away_the_reader_is_taken() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let out = func.new_vreg(GPR);
let lea = op(&mut names, "lea_64");
let load = op(&mut names, "mov_rm_64");
let at = |reg| mir::Mem { disp: 16, ..mir::Mem::at(mir::Operand::read(reg, GPR)) };
let made = func.build(block, lea).def(address, GPR).mem(at(base)).finish();
let read = func
.build(block, load)
.def(out, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
let mut set = Changes::new();
set.rewrite(
read,
Plan {
opcode: load,
operands: vec![mir::Operand::write(out, GPR), mir::Operand::read(base, GPR)],
imm: None,
amode: Some(mir::Amode { disp: 16, ..func[func[read].mem.expect("a load")] }),
symbol: None,
},
);
set.remove(made);
let mut reads = Reads::of(&func);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
assert_eq!(reads.count(address), 0, "nothing reads the address the lea wrote");
assert_eq!(reads.count(base), 1, "the load reads what the lea read");
}
#[test]
fn a_rename_that_takes_the_last_reader_off_an_instruction_lets_it_go() {
let (mut names, mut func, block) = empty();
let (first, into) = copy(&mut func, &mut names, block);
let source = func[func[first].operands][1].reg;
let out = func.new_vreg(GPR);
let mov = func[first].opcode;
let second = func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
let mut set = Changes::new();
set.rename(second, into, source);
set.remove(first);
let mut reads = Reads::of(&func);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"]);
assert_eq!(func[func[second].operands][1].reg, source, "the reader was not sent on");
assert_eq!(reads.count(into), 0, "nothing reads what the copy wrote");
assert_eq!(reads.count(source), 1, "the reader reads what the copy read");
}
#[test]
fn a_removal_whose_reader_is_not_renamed_is_refused() {
let (mut names, mut func, block) = empty();
let (first, into) = copy(&mut func, &mut names, block);
let out = func.new_vreg(GPR);
let mov = func[first].opcode;
func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
let mut set = Changes::new();
set.remove(first);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(first)));
}
#[test]
fn a_rename_into_a_register_of_another_class_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, _) = copy(&mut func, &mut names, block);
let source = func[func[mov].operands][1].reg;
let float = func.new_vreg(XMM);
let mut set = Changes::new();
set.rename(mov, source, float);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Class(mov)));
}
#[test]
fn an_edge_carries_what_the_set_says_and_the_instruction_it_read_goes() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let (mov, into) = copy(&mut func, &mut names, block);
let source = func[func[mov].operands][1].reg;
let arrived = func.new_vreg(GPR);
func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
*func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
let mut set = Changes::new();
set.carry(block, 0, vec![source]);
set.remove(mov);
let mut reads = Reads::of(&func);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
assert!(shape(&func, &names, block).is_empty(), "the copy is still there");
assert_eq!(func[block].succs[0].args, vec![source]);
assert_eq!(reads.count(into), 0);
assert_eq!(reads.count(source), 1, "the edge reads what the copy read");
}
#[test]
fn an_edge_that_is_not_there_is_refused() {
let (mut names, mut func, block) = empty();
copy(&mut func, &mut names, block);
let mut set = Changes::new();
set.carry(block, 0, Vec::new());
assert_eq!(refused(&func, &names, &set), Some(Refusal::Edge(block, 0)));
}
#[test]
fn an_edge_carrying_the_wrong_number_of_values_is_refused() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let (_, into) = copy(&mut func, &mut names, block);
let arrived = func.new_vreg(GPR);
func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
*func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
let mut set = Changes::new();
set.carry(block, 0, vec![into, into]);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Args(block, 0)));
}
#[test]
fn an_answer_an_edge_carries_keeps_its_instruction() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let (mov, into) = copy(&mut func, &mut names, block);
let arrived = func.new_vreg(GPR);
func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
*func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
let mut set = Changes::new();
set.remove(mov);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(mov)));
}
#[test]
fn an_instruction_named_twice_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, _) = copy(&mut func, &mut names, block);
let mut set = Changes::new();
set.rewrite(mov, Plan::of(&func, mov));
set.remove(mov);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Twice(mov)));
}
#[test]
fn an_instruction_that_has_already_gone_is_refused() {
let (mut names, mut func, block) = empty();
let (mov, _) = copy(&mut func, &mut names, block);
let plan = Plan::of(&func, mov);
func.remove_inst(mov);
let mut set = Changes::new();
set.rewrite(mov, plan);
assert_eq!(refused(&func, &names, &set), Some(Refusal::Gone(mov)));
}
#[test]
fn the_instruction_as_it_stands_is_a_proposal_the_target_takes() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let out = func.new_vreg(GPR);
let load = op(&mut names, "mov_rm_64");
let read = func
.build(block, load)
.def(out, GPR)
.mem(mir::Mem { disp: 24, ..mir::Mem::at(mir::Operand::read(base, GPR)) })
.finish();
let plan = Plan::of(&func, read);
let mut set = Changes::new();
set.rewrite(read, plan.clone());
let mut reads = Reads::of(&func);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
assert_eq!(Plan::of(&func, read), plan);
assert_eq!(reads.count(base), 1, "the one read it had before");
}
#[test]
fn a_set_with_one_bad_change_in_it_leaves_the_others_alone() {
let (mut names, mut func, block) = empty();
let (first, into) = copy(&mut func, &mut names, block);
let (second, _) = copy(&mut func, &mut names, block);
let load = op(&mut names, "mov_rm_64");
let mut set = Changes::new();
set.rewrite(
first,
Plan {
opcode: load,
operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
imm: None,
amode: Some(mir::Amode {
base: Some(1),
index: None,
scale: 1,
disp: 0,
symbol: None,
block: None,
reach: mir::Reach::Itself,
segment: None,
}),
symbol: None,
},
);
set.rewrite(second, Plan { imm: Some(3), ..Plan::of(&func, second) });
let mut reads = Reads::of(&func);
assert_eq!(
set.commit(&mut func, &mut reads, &names, &MACHINE),
Err(Refusal::Imm(second)),
"the second change is the one the target turns down"
);
assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64", "x64.mov_rr_64"]);
}
#[test]
fn a_set_with_nothing_in_it_commits() {
let (mut names, mut func, block) = empty();
copy(&mut func, &mut names, block);
let set = Changes::new();
assert!(set.is_empty());
let mut reads = Reads::of(&func);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(0));
assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"]);
}
#[test]
fn the_counts_are_still_right_after_a_commit() {
let (mut names, mut func, block) = empty();
let (first, into) = copy(&mut func, &mut names, block);
let out = func.new_vreg(GPR);
let mov = func[first].opcode;
let second = func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
let mut reads = Reads::of(&func);
assert_eq!(reads.count(into), 1);
let mut set = Changes::new();
set.remove(second);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
assert_eq!(reads.count(into), 0, "the reader went and the count went with it");
let mut set = Changes::new();
set.remove(first);
assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
assert!(shape(&func, &names, block).is_empty());
}
}