use rucc_base::Interner;
use rucc_mir::{Amode, Func, Inst, Opcode, Operand, Reg};
use rucc_target::MachineInsts;
use crate::changes::{Changes, Plan, Reads};
use crate::fold::Pending;
pub const WINDOW: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fold {
pub from: &'static str,
pub into: &'static str,
pub load: &'static str,
pub commutes: bool,
}
pub static FOLDS: &[Fold] = &[
Fold { from: "add_rr_8", into: "add_rm_8", load: "mov_rm_8", commutes: true },
Fold { from: "add_rr_16", into: "add_rm_16", load: "mov_rm_16", commutes: true },
Fold { from: "add_rr_32", into: "add_rm_32", load: "mov_rm_32", commutes: true },
Fold { from: "add_rr_64", into: "add_rm_64", load: "mov_rm_64", commutes: true },
Fold { from: "sub_rr_8", into: "sub_rm_8", load: "mov_rm_8", commutes: false },
Fold { from: "sub_rr_16", into: "sub_rm_16", load: "mov_rm_16", commutes: false },
Fold { from: "sub_rr_32", into: "sub_rm_32", load: "mov_rm_32", commutes: false },
Fold { from: "sub_rr_64", into: "sub_rm_64", load: "mov_rm_64", commutes: false },
Fold { from: "and_rr_8", into: "and_rm_8", load: "mov_rm_8", commutes: true },
Fold { from: "and_rr_16", into: "and_rm_16", load: "mov_rm_16", commutes: true },
Fold { from: "and_rr_32", into: "and_rm_32", load: "mov_rm_32", commutes: true },
Fold { from: "and_rr_64", into: "and_rm_64", load: "mov_rm_64", commutes: true },
Fold { from: "or_rr_8", into: "or_rm_8", load: "mov_rm_8", commutes: true },
Fold { from: "or_rr_16", into: "or_rm_16", load: "mov_rm_16", commutes: true },
Fold { from: "or_rr_32", into: "or_rm_32", load: "mov_rm_32", commutes: true },
Fold { from: "or_rr_64", into: "or_rm_64", load: "mov_rm_64", commutes: true },
Fold { from: "xor_rr_8", into: "xor_rm_8", load: "mov_rm_8", commutes: true },
Fold { from: "xor_rr_16", into: "xor_rm_16", load: "mov_rm_16", commutes: true },
Fold { from: "xor_rr_32", into: "xor_rm_32", load: "mov_rm_32", commutes: true },
Fold { from: "xor_rr_64", into: "xor_rm_64", load: "mov_rm_64", commutes: true },
Fold { from: "imul_rr_16", into: "imul_rm_16", load: "mov_rm_16", commutes: true },
Fold { from: "imul_rr_32", into: "imul_rm_32", load: "mov_rm_32", commutes: true },
Fold { from: "imul_rr_64", into: "imul_rm_64", load: "mov_rm_64", commutes: true },
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Update {
pub from: &'static str,
pub into: &'static str,
pub load: &'static str,
pub store: &'static str,
pub commutes: bool,
}
pub static UPDATES: &[Update] = &[
Update {
from: "add_rr_8",
into: "add_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "add_rr_16",
into: "add_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "add_rr_32",
into: "add_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "add_rr_64",
into: "add_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
Update {
from: "sub_rr_8",
into: "sub_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: false,
},
Update {
from: "sub_rr_16",
into: "sub_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: false,
},
Update {
from: "sub_rr_32",
into: "sub_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: false,
},
Update {
from: "sub_rr_64",
into: "sub_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: false,
},
Update {
from: "and_rr_8",
into: "and_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "and_rr_16",
into: "and_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "and_rr_32",
into: "and_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "and_rr_64",
into: "and_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
Update {
from: "or_rr_8",
into: "or_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "or_rr_16",
into: "or_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "or_rr_32",
into: "or_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "or_rr_64",
into: "or_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
Update {
from: "xor_rr_8",
into: "xor_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "xor_rr_16",
into: "xor_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "xor_rr_32",
into: "xor_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "xor_rr_64",
into: "xor_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Bump {
pub from: &'static str,
pub into: &'static str,
pub load: &'static str,
pub store: &'static str,
}
pub static BUMPS: &[Bump] = &[
Bump { from: "add_ri_8", into: "add_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "add_ri_16", into: "add_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "add_ri_32", into: "add_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "add_ri_64", into: "add_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "sub_ri_8", into: "sub_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "sub_ri_16", into: "sub_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "sub_ri_32", into: "sub_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "sub_ri_64", into: "sub_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "and_ri_8", into: "and_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "and_ri_16", into: "and_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "and_ri_32", into: "and_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "and_ri_64", into: "and_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "or_ri_8", into: "or_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "or_ri_16", into: "or_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "or_ri_32", into: "or_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "or_ri_64", into: "or_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "xor_ri_8", into: "xor_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "xor_ri_16", into: "xor_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "xor_ri_32", into: "xor_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "xor_ri_64", into: "xor_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
];
#[derive(Debug, Clone, Copy)]
struct Waiting {
inst: Inst,
reg: Reg,
load: &'static str,
at: usize,
}
pub fn loads(
func: &mut Func,
machine: &MachineInsts,
names: &mut Interner,
pending: &mut Pending<'_>,
) -> usize {
let mut reads = Reads::of(func);
let mut done = 0;
for block in func.blocks().collect::<Vec<_>>() {
let mut waiting: Option<Waiting> = None;
for (at, inst) in func.insts(block).collect::<Vec<_>>().into_iter().enumerate() {
let name = names.resolve(func[inst].opcode.name()).to_owned();
let bare = machine.bare(&name).to_owned();
let barrier = machine.calls(&name) || !machine.has(&name) || machine.touches_mem(&name);
if let Some(carried) = waiting {
if let Some(plan) = joined(func, &reads, carried, machine, names, inst, &bare) {
let mut set = Changes::new();
set.rewrite(inst, plan);
set.remove(carried.inst);
if set.commit(func, &mut reads, names, machine).is_ok() {
pending.moved(carried.inst, &[inst]);
waiting = None;
done += 1;
}
}
}
if barrier {
waiting = None;
}
if let Some(carried) = waiting {
if at - carried.at >= WINDOW || writes_what_it_reads(func, inst, &carried) {
waiting = None;
}
}
if let Some(load) = FOLDS.iter().find(|fold| fold.load == bare).map(|fold| fold.load) {
let operands = &func[func[inst].operands];
if let Some(first) = operands.first().filter(|operand| operand.role.is_def()) {
waiting = Some(Waiting { inst, reg: first.reg, load, at });
}
}
}
}
done
}
#[derive(Debug, Clone, Copy)]
struct Run {
load: Inst,
alu: Inst,
store: Inst,
update: &'static Update,
kept: Operand,
}
#[derive(Debug, Clone, Copy)]
struct Bumped {
load: Inst,
alu: Inst,
store: Inst,
bump: &'static Bump,
imm: i64,
}
pub fn stores(
func: &mut Func,
machine: &MachineInsts,
names: &mut Interner,
pending: &mut Pending<'_>,
) -> usize {
let mut reads = Reads::of(func);
let mut done = 0;
for block in func.blocks().collect::<Vec<_>>() {
let insts: Vec<Inst> = func.insts(block).collect();
for at in 0..insts.len() {
let found = match run(func, &reads, machine, names, &insts, at) {
Some(found) => Some((
found.load,
found.alu,
found.store,
updated(func, machine, names, &found),
)),
None => constant(func, &reads, machine, names, &insts, at).map(|found| {
(found.load, found.alu, found.store, bumped(func, machine, names, &found))
}),
};
let Some((load, alu, store, plan)) = found else { continue };
if !pending.alike(load, store) {
continue;
}
let mut set = Changes::new();
set.rewrite(store, plan);
set.remove(alu);
set.remove(load);
if set.commit(func, &mut reads, names, machine).is_ok() {
pending.moved(load, &[]);
done += 1;
}
}
}
done
}
fn run(
func: &Func,
reads: &Reads,
machine: &MachineInsts,
names: &Interner,
insts: &[Inst],
at: usize,
) -> Option<Run> {
let store = insts[at];
let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
let value = *func[func[store].operands].first()?;
if value.role.is_def() || reads.count(value.reg) != 1 {
return None;
}
let earliest = at.saturating_sub(WINDOW);
let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
let update = UPDATES.iter().find(|row| row.from == bare && row.store == stored)?;
let operands = func[func[insts[alu]].operands].to_vec();
let [_, first, second] = operands[..] else { return None };
let both = [(first, second), (second, first)];
let tried = if update.commutes { &both[..] } else { &both[..1] };
for &(source, kept) in tried {
if reads.count(source.reg) != 1 {
continue;
}
let Some(from) = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg)) else {
continue;
};
let load = insts[from];
if machine.bare(names.resolve(func[load].opcode.name())) != update.load {
continue;
}
if !same_place(func, load, store) {
continue;
}
let mut wanted: Vec<Reg> =
func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
wanted.push(kept.reg);
if !clear(func, machine, names, insts, (from, at), &wanted) {
continue;
}
return Some(Run { load, alu: insts[alu], store, update, kept });
}
None
}
fn constant(
func: &Func,
reads: &Reads,
machine: &MachineInsts,
names: &Interner,
insts: &[Inst],
at: usize,
) -> Option<Bumped> {
let store = insts[at];
let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
let value = *func[func[store].operands].first()?;
if value.role.is_def() || reads.count(value.reg) != 1 {
return None;
}
let mem = func[func[store].mem?];
if mem.base == Some(0) || mem.index == Some(0) {
return None;
}
let earliest = at.saturating_sub(WINDOW);
let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
let bump = BUMPS.iter().find(|row| row.from == bare && row.store == stored)?;
let operands = func[func[insts[alu]].operands].to_vec();
let [_, source] = operands[..] else { return None };
let imm = func[func[insts[alu]].imm?].0;
if reads.count(source.reg) != 1 {
return None;
}
let from = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg))?;
let load = insts[from];
if machine.bare(names.resolve(func[load].opcode.name())) != bump.load {
return None;
}
if !same_place(func, load, store) {
return None;
}
let wanted: Vec<Reg> =
func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
if !clear(func, machine, names, insts, (from, at), &wanted) {
return None;
}
Some(Bumped { load, alu: insts[alu], store, bump, imm })
}
fn writes(func: &Func, inst: Inst, reg: Reg) -> bool {
func[func[inst].operands].iter().any(|operand| operand.role.is_def() && operand.reg == reg)
}
fn same_place(func: &Func, one: Inst, other: Inst) -> bool {
let (Some(here), Some(there)) = (func[one].mem, func[other].mem) else { return false };
let (here, there) = (func[here], func[there]);
if func[one].symbol != func[other].symbol {
return false;
}
let bare = |amode: Amode| Amode { base: None, index: None, ..amode };
if bare(here) != bare(there) {
return false;
}
let same = |left: Option<u8>, right: Option<u8>| match (left, right) {
(None, None) => true,
(Some(left), Some(right)) => {
func[func[one].operands][usize::from(left)].reg
== func[func[other].operands][usize::from(right)].reg
}
_ => false,
};
same(here.base, there.base) && same(here.index, there.index)
}
fn clear(
func: &Func,
machine: &MachineInsts,
names: &Interner,
insts: &[Inst],
span: (usize, usize),
wanted: &[Reg],
) -> bool {
let (from, to) = span;
insts[from + 1..to].iter().all(|&inst| {
let name = names.resolve(func[inst].opcode.name());
if machine.calls(name) || !machine.has(name) || machine.touches_mem(name) {
return false;
}
!func[func[inst].operands]
.iter()
.any(|operand| operand.role.is_def() && wanted.contains(&operand.reg))
})
}
fn updated(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Run) -> Plan {
let operands = func[func[run.store].operands].to_vec();
let into = names.intern(&format!("{}{}", machine.prefix, run.update.into));
Plan {
opcode: Opcode::new(into),
operands: [run.kept].into_iter().chain(operands[1..].iter().copied()).collect(),
imm: None,
amode: func[run.store].mem.map(|mem| func[mem]),
symbol: func[run.store].symbol,
}
}
fn bumped(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Bumped) -> Plan {
let operands = func[func[run.store].operands][1..].to_vec();
let into = names.intern(&format!("{}{}", machine.prefix, run.bump.into));
let back = |at: Option<u8>| at.map(|at| at - 1);
Plan {
opcode: Opcode::new(into),
operands,
imm: Some(run.imm),
amode: func[run.store].mem.map(|mem| {
let mem = func[mem];
Amode { base: back(mem.base), index: back(mem.index), ..mem }
}),
symbol: func[run.store].symbol,
}
}
fn writes_what_it_reads(func: &Func, inst: Inst, carried: &Waiting) -> bool {
let written: Vec<Reg> = func[func[inst].operands]
.iter()
.filter(|operand| operand.role.is_def())
.map(|operand| operand.reg)
.collect();
func[func[carried.inst].operands].iter().any(|operand| written.contains(&operand.reg))
}
fn joined(
func: &Func,
reads: &Reads,
carried: Waiting,
machine: &MachineInsts,
names: &mut Interner,
inst: Inst,
bare: &str,
) -> Option<Plan> {
let fold = FOLDS.iter().find(|fold| fold.from == bare)?;
if carried.load != fold.load || reads.count(carried.reg) != 1 {
return None;
}
let operands = func[func[inst].operands].to_vec();
let [answer, first, second] = operands[..] else { return None };
let kept = if second.reg == carried.reg {
first
} else if fold.commutes && first.reg == carried.reg {
second
} else {
return None;
};
let load = carried.inst;
let address = func[func[load].operands][1..].to_vec();
let mut amode = func[func[load].mem?];
amode.base = amode.base.map(|at| at + 1);
amode.index = amode.index.map(|at| at + 1);
let into = names.intern(&format!("{}{}", machine.prefix, fold.into));
Some(Plan {
opcode: Opcode::new(into),
operands: [answer, kept].into_iter().chain(address).collect(),
imm: None,
amode: Some(amode),
symbol: func[load].symbol,
})
}
#[cfg(test)]
mod tests {
use rucc_mir::{self as mir, Constraint, Mem, Operand};
use rucc_target::x86_64::{GPR, MACHINE};
use super::*;
fn empty() -> (Interner, Func, mir::Block) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let block = func.create_block();
(names, func, block)
}
fn op(names: &mut Interner, name: &str) -> Opcode {
Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
}
fn load(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg) -> Reg {
let into = func.new_vreg(GPR);
let mov = op(names, "mov_rm_64");
func.build(block, mov)
.def(into, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
into
}
fn alu(
func: &mut Func,
names: &mut Interner,
block: mir::Block,
name: &str,
first: Reg,
second: Reg,
) -> Reg {
let answer = func.new_vreg(GPR);
let opcode = op(names, name);
func.build(block, opcode)
.operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
.uses(first, GPR)
.uses(second, GPR)
.finish();
answer
}
fn shape(func: &Func, names: &Interner, block: mir::Block) -> Vec<String> {
func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
}
fn combine(func: &mut Func, names: &mut Interner) -> usize {
let mut addresses = Vec::new();
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
loads(func, &MACHINE, names, &mut pending)
}
fn store(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg, value: Reg) {
let mov = op(names, "mov_mr_64");
func.build(block, mov)
.uses(value, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
}
fn update(func: &mut Func, names: &mut Interner) -> usize {
let mut addresses = Vec::new();
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
stores(func, &MACHINE, names, &mut pending)
}
#[test]
fn a_word_read_changed_and_written_back_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
let inst = func.insts(block).next().expect("the addition");
let mem = func[inst].mem.expect("it writes memory");
assert_eq!(func[mem].disp, 16, "the address came from the store");
assert_eq!(func[mem].base, Some(1), "and names the operand behind the source");
assert_eq!(func[func[inst].operands].len(), 2, "one source and the base of the address");
assert_eq!(func[func[inst].operands][0].reg, other, "the source it kept");
assert_eq!(func[func[inst].operands][1].reg, base, "the address");
}
#[test]
fn a_word_read_into_the_right_source_of_an_addition_is_still_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", other, word);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
assert_eq!(func[func[func.insts(block).next().expect("it")].operands][0].reg, other);
}
#[test]
fn a_subtraction_taking_a_register_away_from_memory_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let left = alu(&mut func, &mut names, block, "sub_rr_64", word, other);
store(&mut func, &mut names, block, base, left);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.sub_mr_64"]);
}
#[test]
fn a_subtraction_taking_memory_away_from_a_register_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let left = alu(&mut func, &mut names, block, "sub_rr_64", other, word);
store(&mut func, &mut names, block, base, left);
assert_eq!(update(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.sub_rr_64", "x64.mov_mr_64"]
);
}
#[test]
fn a_store_to_another_address_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let elsewhere = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, elsewhere, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_store_at_another_displacement_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
let mov = op(&mut names, "mov_mr_64");
func.build(block, mov)
.uses(sum, GPR)
.mem(Mem { disp: 24, ..Mem::at(Operand::read(base, GPR)) })
.finish();
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_word_two_instructions_read_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
alu(&mut func, &mut names, block, "xor_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn an_answer_something_else_reads_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
alu(&mut func, &mut names, block, "xor_rr_64", sum, other);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_run_with_another_access_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
load(&mut func, &mut names, block, other);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sub = op(&mut names, "sub_ri_64");
func.build(block, sub)
.operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
.uses(base, GPR)
.imm(32)
.finish();
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn two_locals_the_layout_has_not_placed_yet_are_not_the_same_place() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 4usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
}
#[test]
fn the_frame_entry_of_a_load_that_goes_comes_off_the_list() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 3usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
}
#[test]
fn a_run_whose_widths_disagree_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let narrow = op(&mut names, "mov_rm_32");
func.build(block, narrow)
.def(into, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
let sum = alu(&mut func, &mut names, block, "add_rr_64", into, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn every_row_of_the_update_table_is_four_instructions_this_target_has() {
for update in UPDATES {
for name in [update.from, update.into, update.load, update.store] {
assert!(MACHINE.has(name), "{name} is not an instruction");
}
let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
assert_eq!(width(update.from), width(update.into), "{} changes width", update.from);
assert_eq!(
width(update.from),
width(update.load),
"{} loads another width",
update.from
);
assert_eq!(
width(update.from),
width(update.store),
"{} stores another width",
update.from
);
assert!((MACHINE.takes_mem)(update.into), "{} reaches no memory", update.into);
assert!(!(MACHINE.takes_mem)(update.from), "{} already reaches memory", update.from);
}
}
#[test]
fn the_update_table_covers_the_arithmetic_this_target_can_do_in_place() {
assert_eq!(UPDATES.len(), 20, "five operations at four widths, and no multiply");
let commuting = UPDATES.iter().filter(|update| update.commutes).count();
assert_eq!(commuting, 16, "everything but the four subtractions");
}
fn alu_imm(
func: &mut Func,
names: &mut Interner,
block: mir::Block,
name: &str,
source: Reg,
value: i64,
) -> Reg {
let answer = func.new_vreg(GPR);
let opcode = op(names, name);
func.build(block, opcode)
.operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
.uses(source, GPR)
.imm(value)
.finish();
answer
}
#[test]
fn a_word_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_mi_64"]);
let inst = func.insts(block).next().expect("the addition");
let mem = func[inst].mem.expect("it writes memory");
assert_eq!(func[mem].disp, 16, "the address came from the store");
assert_eq!(func[mem].base, Some(0), "which is now the first operand and not the second");
assert_eq!(func[func[inst].operands].len(), 1, "the base of the address and nothing else");
assert_eq!(func[func[inst].operands][0].reg, base, "the address");
assert_eq!(func[func[inst].imm.expect("the constant")].0, 1);
}
#[test]
fn a_constant_taken_away_from_a_place_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let left = alu_imm(&mut func, &mut names, block, "sub_ri_64", word, 7);
store(&mut func, &mut names, block, base, left);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.sub_mi_64"]);
assert_eq!(func[func[func.insts(block).next().expect("it")].imm.expect("it")].0, 7);
}
#[test]
fn a_byte_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = func.new_vreg(GPR);
let mov = op(&mut names, "mov_rm_8");
func.build(block, mov)
.def(word, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
let sum = alu_imm(&mut func, &mut names, block, "or_ri_8", word, 4);
let put = op(&mut names, "mov_mr_8");
func.build(block, put)
.uses(sum, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.or_mi_8"]);
}
#[test]
fn a_word_a_constant_changes_and_something_else_reads_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
alu(&mut func, &mut names, block, "xor_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_with_another_access_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let elsewhere = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
load(&mut func, &mut names, block, elsewhere);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RAX);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
let mov = op(&mut names, "mov_ri_64");
func.build(block, mov).def(base, GPR).imm(0).finish();
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_written_to_another_address_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let elsewhere = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
store(&mut func, &mut names, block, elsewhere, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_whose_widths_disagree_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let narrow = op(&mut names, "mov_rm_32");
func.build(block, narrow)
.def(into, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", into, 1);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_place_multiplied_by_a_constant_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let product = alu_imm(&mut func, &mut names, block, "imul_ri_64", word, 3);
store(&mut func, &mut names, block, base, product);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn the_frame_entry_of_a_load_a_constant_run_takes_comes_off_the_list() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 3usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
}
#[test]
fn two_locals_a_constant_run_would_join_are_not_the_same_place() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 4usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
}
#[test]
fn every_row_of_the_bump_table_is_four_instructions_this_target_has() {
for bump in BUMPS {
for name in [bump.from, bump.into, bump.load, bump.store] {
assert!(MACHINE.has(name), "{name} is not an instruction");
}
let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
assert_eq!(width(bump.from), width(bump.into), "{} changes width", bump.from);
assert_eq!(width(bump.from), width(bump.load), "{} loads another width", bump.from);
assert_eq!(width(bump.from), width(bump.store), "{} stores another width", bump.from);
assert!((MACHINE.takes_mem)(bump.into), "{} reaches no memory", bump.into);
assert!(!(MACHINE.takes_mem)(bump.from), "{} already reaches memory", bump.from);
assert!((MACHINE.takes_imm)(bump.into), "{} carries no constant", bump.into);
}
}
#[test]
fn the_bump_table_covers_the_arithmetic_this_target_can_do_in_place_against_a_constant() {
assert_eq!(BUMPS.len(), 20, "five operations at four widths, and no multiply");
let register: Vec<&str> = UPDATES.iter().map(|update| update.from).collect();
for bump in BUMPS {
let same = bump.from.replace("_ri_", "_rr_");
assert!(register.contains(&same.as_str()), "{} has no register row", bump.from);
}
}
#[test]
fn nothing_is_both_a_register_run_and_a_constant_run() {
for bump in BUMPS {
assert!(
!UPDATES.iter().any(|update| update.from == bump.from),
"{} starts both kinds of run",
bump.from
);
}
}
#[test]
fn a_load_read_once_by_an_addition_becomes_its_memory_operand() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
let inst = func.insts(block).next().expect("the addition");
let mem = func[inst].mem.expect("the addition reads memory now");
assert_eq!(func[mem].disp, 16, "the load's displacement came with it");
assert_eq!(func[mem].base, Some(2), "and names the operand behind the source it kept");
assert_eq!(func[func[inst].operands][1].reg, other, "the source it kept");
assert_eq!(func[func[inst].operands][2].reg, base, "the address it took on");
}
#[test]
fn a_load_feeding_the_first_source_of_an_addition_is_swapped_and_folded() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", word, other);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(func[func[inst].operands][1].reg, other);
}
#[test]
fn a_load_feeding_the_left_of_a_subtraction_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "sub_rr_64", word, other);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.sub_rr_64"]);
}
#[test]
fn a_load_feeding_the_right_of_a_subtraction_folds() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "sub_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.sub_rm_64"]);
}
#[test]
fn a_load_two_instructions_read_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
alu(&mut func, &mut names, block, "xor_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.add_rr_64", "x64.xor_rr_64"]
);
}
#[test]
fn a_load_with_a_store_between_it_and_its_reader_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let store = op(&mut names, "mov_mr_64");
func.build(block, store).uses(other, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.mov_mr_64", "x64.add_rr_64"]
);
}
#[test]
fn a_load_with_another_load_between_it_and_its_reader_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
load(&mut func, &mut names, block, other);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.mov_rm_64", "x64.add_rr_64"]
);
}
#[test]
fn the_later_of_two_loads_is_the_one_that_folds() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let first = load(&mut func, &mut names, block, base);
let second = load(&mut func, &mut names, block, other);
alu(&mut func, &mut names, block, "add_rr_64", first, second);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rm_64"]);
let addition = func.insts(block).nth(1).expect("the addition");
assert_eq!(func[func[addition].operands][1].reg, first, "the earlier load is still read");
assert_eq!(func[func[addition].operands][2].reg, other, "and the later one is the address");
}
#[test]
fn a_load_with_a_call_between_it_and_its_reader_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let call = op(&mut names, "call");
func.build(block, call).finish();
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.call", "x64.add_rr_64"]);
}
#[test]
fn a_load_whose_address_register_is_written_between_the_two_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sub = op(&mut names, "sub_ri_64");
func.build(block, sub)
.operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
.uses(base, GPR)
.imm(32)
.finish();
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
}
#[test]
fn a_load_of_the_wrong_width_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let narrow = op(&mut names, "mov_rm_32");
func.build(block, narrow).def(into, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
alu(&mut func, &mut names, block, "add_rr_64", other, into);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_32", "x64.add_rr_64"]);
}
#[test]
fn a_load_whose_value_an_edge_carries_stays_a_load() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
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![word])];
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rr_64"]);
}
#[test]
fn a_reader_in_another_block_stays_where_it_is() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, next, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
assert_eq!(shape(&func, &names, next), ["x64.add_rr_64"]);
}
#[test]
fn a_reader_past_the_window_stays_where_it_is() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let nop = op(&mut names, "nop");
for _ in 0..WINDOW {
func.build(block, nop).finish();
}
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
}
#[test]
fn a_reader_at_the_edge_of_the_window_folds() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let nop = op(&mut names, "nop");
for _ in 0..WINDOW - 1 {
func.build(block, nop).finish();
}
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 1);
}
#[test]
fn the_frame_entry_of_a_load_that_moves_goes_with_it() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let reader = func.insts(block).nth(1);
assert!(reader.is_none(), "the block holds the load alone so far");
alu(&mut func, &mut names, block, "add_rr_64", other, word);
let held = func.insts(block).next().expect("the load");
let mut addresses = vec![(held, 3usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(loads(&mut func, &MACHINE, &mut names, &mut pending), 1);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(addresses, [(inst, 3usize)], "the entry names the instruction that took it");
}
#[test]
fn every_row_of_the_table_is_three_instructions_this_target_has() {
for fold in FOLDS {
assert!(MACHINE.has(fold.from), "{} is not an instruction", fold.from);
assert!(MACHINE.has(fold.into), "{} is not an instruction", fold.into);
assert!(MACHINE.has(fold.load), "{} is not an instruction", fold.load);
let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
assert_eq!(width(fold.from), width(fold.into), "{} changes width", fold.from);
assert_eq!(width(fold.from), width(fold.load), "{} loads another width", fold.from);
assert!((MACHINE.takes_mem)(fold.into), "{} reads no memory", fold.into);
assert!(!(MACHINE.takes_mem)(fold.from), "{} already reads memory", fold.from);
}
}
#[test]
fn the_table_covers_the_arithmetic_this_target_has() {
assert_eq!(FOLDS.len(), 23, "six operations at four widths, less the eight bit multiply");
let commuting = FOLDS.iter().filter(|fold| fold.commutes).count();
assert_eq!(commuting, 19, "everything but the four subtractions");
}
}