use std::collections::HashMap;
use rucc_base::Interner;
use rucc_mir as mir;
use rucc_target::{FrameInsts, MachineInsts, Role};
use crate::changes::{Changes, Plan, Reads};
#[derive(Debug)]
pub struct Pending<'a> {
pub addresses: &'a mut Vec<(mir::Inst, usize)>,
pub arguments: &'a mut Vec<(mir::Inst, u32)>,
pub dynamic: &'a mut Vec<mir::Inst>,
}
impl Pending<'_> {
pub(crate) fn moved(&mut self, from: mir::Inst, into: &[mir::Inst]) {
move_entries(self.addresses, from, into);
move_entries(self.arguments, from, into);
if let Some(at) = self.dynamic.iter().position(|&inst| inst == from) {
self.dynamic.splice(at..=at, into.iter().copied());
}
}
fn holds(&self, inst: mir::Inst) -> bool {
let named = self.addresses.iter().map(|&(at, _)| at);
let listed = named.chain(self.arguments.iter().map(|&(at, _)| at));
listed.chain(self.dynamic.iter().copied()).any(|at| at == inst)
}
}
const FRAME_READERS: usize = 3;
fn move_entries<T: Copy>(list: &mut Vec<(mir::Inst, T)>, from: mir::Inst, into: &[mir::Inst]) {
let Some(at) = list.iter().position(|&(inst, _)| inst == from) else { return };
let (_, what) = list[at];
list.splice(at..=at, into.iter().map(|&inst| (inst, what)));
}
pub fn addresses(
func: &mut mir::Func,
insts: &FrameInsts,
machine: &MachineInsts,
names: &mut Interner,
pending: &mut Pending<'_>,
) -> usize {
let lea = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.lea)));
let mut reads = Reads::of(func);
let mut folded = 0;
for block in func.blocks().collect::<Vec<_>>() {
let mut open: HashMap<mir::Reg, Open> = HashMap::new();
for inst in func.insts(block).collect::<Vec<_>>() {
if let Some(ready) = offer(func, &mut open, inst) {
let mut set = Changes::new();
for folding in &ready.folds {
let plan = Plan {
operands: folding.operands.clone(),
amode: Some(folding.amode),
..Plan::of(func, folding.into)
};
set.rewrite(folding.into, plan);
}
set.remove(ready.from);
if set.commit(func, &mut reads, names, machine).is_ok() {
folded += ready.folds.len();
let took: Vec<mir::Inst> = ready.folds.iter().map(|fold| fold.into).collect();
pending.moved(ready.from, &took);
open.retain(|_, held| held.folds.iter().all(|fold| fold.into != ready.from));
}
}
for written in written(func, inst) {
open.retain(|reg, held| *reg != written && !touches(func, held.from, written));
}
if func[inst].opcode == lea {
let room = if pending.holds(inst) { FRAME_READERS } else { usize::MAX };
match folding_def(func, &reads, inst) {
Some((reg, wanted))
if wanted <= room && (wanted == 1 || fits_every_reader(func, inst)) =>
{
open.insert(reg, Open { from: inst, wanted, folds: Vec::new() });
}
_ => {}
}
}
}
}
folded
}
struct Open {
from: mir::Inst,
wanted: usize,
folds: Vec<Folding>,
}
fn offer(func: &mir::Func, open: &mut HashMap<mir::Reg, Open>, inst: mir::Inst) -> Option<Open> {
let folding = candidate(func, open, inst);
let takes = |reg: mir::Reg| folding.as_ref().is_some_and(|fold| fold.base == reg);
let refused: Vec<mir::Reg> = open
.keys()
.copied()
.filter(|®| {
let times = times_read(func, inst, reg);
times > 0 && !(times == 1 && takes(reg))
})
.collect();
for reg in refused {
open.remove(®);
}
let folding = folding?;
let base = folding.base;
let held = open.get_mut(&base)?;
held.folds.push(folding);
if held.folds.len() < held.wanted {
return None;
}
open.remove(&base)
}
fn fits_every_reader(func: &mir::Func, inst: mir::Inst) -> bool {
func[inst].mem.is_some_and(|mem| func[mem].symbol.is_none())
}
fn times_read(func: &mir::Func, inst: mir::Inst, reg: mir::Reg) -> usize {
func[func[inst].operands]
.iter()
.filter(|operand| operand.role == Role::Use && operand.reg == reg)
.count()
}
fn folding_def(func: &mir::Func, reads: &Reads, inst: mir::Inst) -> Option<(mir::Reg, usize)> {
let operands = &func[func[inst].operands];
let mut defs = operands.iter().filter(|operand| operand.role != Role::Use);
let def = defs.next()?;
if defs.next().is_some() || !def.reg.is_virtual() {
return None;
}
let wanted = reads.count(def.reg);
(wanted > 0).then_some((def.reg, wanted))
}
fn written(func: &mir::Func, inst: mir::Inst) -> Vec<mir::Reg> {
func[func[inst].operands]
.iter()
.filter(|operand| operand.role != Role::Use)
.map(|operand| operand.reg)
.collect()
}
fn touches(func: &mir::Func, inst: mir::Inst, reg: mir::Reg) -> bool {
let Some(mem) = func[inst].mem else { return false };
let amode = func[mem];
let operands = &func[func[inst].operands];
[amode.base, amode.index]
.into_iter()
.flatten()
.filter_map(|at| operands.get(usize::from(at)))
.any(|operand| operand.reg == reg)
}
fn base_reg(func: &mir::Func, inst: mir::Inst) -> Option<mir::Reg> {
let amode = func[func[inst].mem?];
if amode.index.is_some() || amode.symbol.is_some() || amode.reach != mir::Reach::Itself {
return None;
}
Some(func[func[inst].operands].get(usize::from(amode.base?))?.reg)
}
struct Folding {
into: mir::Inst,
base: mir::Reg,
operands: Vec<mir::Operand>,
amode: mir::Amode,
}
fn candidate(func: &mir::Func, open: &HashMap<mir::Reg, Open>, inst: mir::Inst) -> Option<Folding> {
let base = base_reg(func, inst)?;
let from = open.get(&base)?.from;
let address = func[func[from].mem?];
let disp = i64::from(address.disp) + i64::from(func[func[inst].mem?].disp);
let mut amode = mir::Amode { disp: i32::try_from(disp).ok()?, ..address };
let taken = &func[func[from].operands];
let reader = &func[func[inst].operands];
let mut operands = reader.get(..reader.len().checked_sub(1)?)?.to_vec();
for (at, into) in [(address.base, &mut amode.base), (address.index, &mut amode.index)] {
let Some(at) = at else { continue };
operands.push(*taken.get(usize::from(at))?);
*into = Some(u8::try_from(operands.len() - 1).ok()?);
}
Some(Folding { into: inst, base, operands, amode })
}
#[cfg(test)]
mod tests {
use rucc_target::x86_64::{FRAME, GPR, MACHINE, RDI};
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 folds(func: &mut mir::Func, names: &mut Interner) -> usize {
let (mut locals, mut arguments, mut growable) = (Vec::new(), Vec::new(), Vec::new());
addresses(
func,
&FRAME,
&MACHINE,
names,
&mut Pending {
addresses: &mut locals,
arguments: &mut arguments,
dynamic: &mut growable,
},
)
}
fn op(names: &mut Interner, name: &str) -> mir::Opcode {
mir::Opcode::new(names.intern(&format!("{}{name}", FRAME.prefix)))
}
fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<(String, mir::Amode)> {
func.insts(block)
.map(|inst| {
let amode = func[inst].mem.map_or(mir::Amode::NOTHING, |mem| func[mem]);
(names.resolve(func[inst].opcode.name()).to_owned(), amode)
})
.collect()
}
fn address_regs(func: &mir::Func, inst: mir::Inst) -> Vec<mir::Reg> {
let amode = func[func[inst].mem.expect("a memory operand")];
let operands = &func[func[inst].operands];
[amode.base, amode.index]
.into_iter()
.flatten()
.map(|at| operands[usize::from(at)].reg)
.collect()
}
#[test]
fn an_address_a_load_reads_once_becomes_the_load_s_own_addressing_mode() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(
mir::Mem::at(mir::Operand::read(array, GPR))
.indexed(mir::Operand::read(index, GPR), 4),
)
.finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
assert_eq!(folds(&mut func, &mut names), 1);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 1, "the address is worked out twice: {left:?}");
assert_eq!(left[0].0, format!("{}mov_rm_32", FRAME.prefix));
assert_eq!(left[0].1.scale, 4);
assert_eq!(left[0].1.disp, 0);
let inst = func.insts(block).next().expect("the load is still there");
assert_eq!(address_regs(&func, inst), vec![array, index], "the load reads the wrong pair");
}
#[test]
fn the_displacements_of_the_two_addresses_are_added() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(8))
.finish();
assert_eq!(folds(&mut func, &mut names), 1);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 1);
assert_eq!(left[0].1.disp, 24, "the field is at the sum of the two offsets or nowhere");
}
#[test]
fn a_store_keeps_the_value_it_is_storing() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let store = op(&mut names, "mov_mr_32");
func.build(block, lea)
.def(address, GPR)
.mem(
mir::Mem::at(mir::Operand::read(array, GPR))
.indexed(mir::Operand::read(index, GPR), 8),
)
.finish();
func.build(block, store)
.uses(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
assert_eq!(folds(&mut func, &mut names), 1);
let inst = func.insts(block).next().expect("the store is still there");
let regs: Vec<mir::Reg> = func[func[inst].operands].iter().map(|op| op.reg).collect();
assert_eq!(regs, vec![value, array, index], "the value the store writes went missing");
assert_eq!(func[func[inst].mem.expect("a memory operand")].scale, 8);
}
#[test]
fn an_address_every_reader_can_take_is_folded_into_all_of_them() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let store = op(&mut names, "mov_mr_32");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
for offset in [0, 12, 28] {
func.build(block, store)
.uses(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(offset))
.finish();
}
assert_eq!(folds(&mut func, &mut names), 3);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 3, "the address is still worked out on its own: {left:?}");
let disps: Vec<i32> = left.iter().map(|(_, amode)| amode.disp).collect();
assert_eq!(disps, vec![16, 28, 44], "each store is at its own offset from the address");
for inst in func.insts(block).collect::<Vec<_>>() {
assert_eq!(address_regs(&func, inst), vec![array]);
}
}
#[test]
fn an_address_one_reader_cannot_take_is_folded_into_none_of_them() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
for at in 0..3 {
let value = func.new_vreg(GPR);
let mem = mir::Mem::at(mir::Operand::read(address, GPR));
let mem = if at == 1 { mem.indexed(mir::Operand::read(index, GPR), 4) } else { mem };
func.build(block, load).def(value, GPR).mem(mem).finish();
}
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 4);
}
#[test]
fn an_indexed_address_every_reader_can_take_is_folded_into_all_of_them() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(
mir::Mem::at(mir::Operand::read(array, GPR))
.indexed(mir::Operand::read(index, GPR), 4),
)
.finish();
for offset in [0, 8] {
let value = func.new_vreg(GPR);
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(offset))
.finish();
}
assert_eq!(folds(&mut func, &mut names), 2);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 2, "the address is gone and both loads carry it: {left:?}");
let disps: Vec<i32> = left.iter().map(|(_, amode)| amode.disp).collect();
assert_eq!(disps, vec![0, 8], "each load is at its own offset from the address");
for inst in func.insts(block).collect::<Vec<_>>() {
assert_eq!(address_regs(&func, inst), vec![array, index]);
}
}
#[test]
fn a_symbol_address_with_more_than_one_reader_is_left_where_it_is() {
let (mut names, mut func, block) = empty();
let address = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
let cell = names.intern("cell");
func.build(block, lea).def(address, GPR).mem(mir::Mem::of(cell)).finish();
for offset in [0, 8] {
let value = func.new_vreg(GPR);
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(offset))
.finish();
}
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 3);
}
#[test]
fn an_address_read_outside_the_block_as_well_is_left_where_it_is() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
for at in [block, next] {
let value = func.new_vreg(GPR);
func.build(at, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
}
*func.succs_mut(block) = vec![mir::BlockCall::to(next)];
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
#[test]
fn a_write_between_one_reader_and_the_next_ends_the_chance_for_the_set() {
let (mut names, mut func, block) = empty();
let array = mir::Reg::physical(RDI);
let address = func.new_vreg(GPR);
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
let put = op(&mut names, "mov_ri_64");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, load)
.def(first, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
func.build(block, put).def(array, GPR).imm(7).finish();
func.build(block, load)
.def(second, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(4))
.finish();
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 4);
}
#[test]
fn an_address_something_reads_as_a_plain_operand_is_left_where_it_is() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
let add = op(&mut names, "add_rr_64");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
func.build(block, add).def(sum, GPR).uses(address, GPR).finish();
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 3);
}
#[test]
fn an_address_the_one_instruction_reads_twice_is_left_where_it_is() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let store = op(&mut names, "mov_mr_64");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, store)
.uses(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
#[test]
fn a_chain_whose_middle_goes_first_leaves_the_outer_address_for_the_next_run() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let outer = func.new_vreg(GPR);
let inner = func.new_vreg(GPR);
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(outer, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, lea)
.def(inner, GPR)
.mem(mir::Mem::at(mir::Operand::read(outer, GPR)).plus(4))
.finish();
func.build(block, load)
.def(first, GPR)
.mem(mir::Mem::at(mir::Operand::read(inner, GPR)))
.finish();
func.build(block, load)
.def(second, GPR)
.mem(mir::Mem::at(mir::Operand::read(outer, GPR)).plus(8))
.finish();
assert_eq!(folds(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block).len(), 3, "the inner address is still there");
assert_eq!(folds(&mut func, &mut names), 2);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 2, "the outer address is still there: {left:?}");
let disps: Vec<i32> = left.iter().map(|(_, amode)| amode.disp).collect();
assert_eq!(disps, vec![20, 24], "the two loads are at the two composed offsets");
}
#[test]
fn a_reader_that_already_has_an_index_is_left_alone() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, load)
.def(value, GPR)
.mem(
mir::Mem::at(mir::Operand::read(address, GPR))
.indexed(mir::Operand::read(index, GPR), 4),
)
.finish();
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
#[test]
fn two_displacements_that_do_not_fit_together_are_not_put_together() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(i32::MAX))
.finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(1))
.finish();
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
#[test]
fn a_register_the_address_reads_being_written_in_between_ends_the_chance() {
let (mut names, mut func, block) = empty();
let array = mir::Reg::physical(RDI);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
let put = op(&mut names, "mov_ri_64");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, put).def(array, GPR).imm(7).finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 3);
}
#[test]
fn a_reader_in_another_block_is_not_one_this_folds_into() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
*func.succs_mut(block) = vec![mir::BlockCall::to(next)];
func.build(next, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
assert_eq!(folds(&mut func, &mut names), 0);
}
#[test]
fn a_chain_of_two_addresses_is_folded_the_whole_way_in_one_pass() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let element = func.new_vreg(GPR);
let field = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea)
.def(element, GPR)
.mem(
mir::Mem::at(mir::Operand::read(array, GPR))
.indexed(mir::Operand::read(index, GPR), 8),
)
.finish();
func.build(block, lea)
.def(field, GPR)
.mem(mir::Mem::at(mir::Operand::read(element, GPR)).plus(4))
.finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(field, GPR)))
.finish();
assert_eq!(folds(&mut func, &mut names), 2);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 1, "one of the two addresses is still its own instruction");
assert_eq!(left[0].1.scale, 8);
assert_eq!(left[0].1.disp, 4);
let inst = func.insts(block).next().expect("the load is still there");
assert_eq!(address_regs(&func, inst), vec![array, index]);
}
#[test]
fn an_address_of_a_global_folds_into_the_reader_symbol_and_all() {
let (mut names, mut func, block) = empty();
let global = names.intern("counters");
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
func.build(block, lea).def(address, GPR).mem(mir::Mem::of(global)).finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(12))
.finish();
assert_eq!(folds(&mut func, &mut names), 1);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 1);
assert_eq!(left[0].1.symbol, Some(global));
assert_eq!(left[0].1.disp, 12);
}
#[test]
fn an_address_whose_displacement_is_still_to_be_written_folds_and_takes_its_entry_with_it() {
let (mut names, mut func, block) = empty();
let sp = mir::Reg::physical(RDI);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
let local = func
.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(sp, GPR)))
.finish();
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(8))
.finish();
let (mut locals, mut arguments, mut growable) = (vec![(local, 3)], Vec::new(), Vec::new());
let mut pending =
Pending { addresses: &mut locals, arguments: &mut arguments, dynamic: &mut growable };
assert_eq!(addresses(&mut func, &FRAME, &MACHINE, &mut names, &mut pending), 1);
let left = shape(&func, &names, block);
assert_eq!(left.len(), 1, "the address is worked out twice: {left:?}");
assert_eq!(left[0].1.disp, 8, "the field's offset is what finish adds the frame's to");
let reader = func.insts(block).next().expect("the load is still there");
assert_eq!(locals, vec![(reader, 3)], "the offset is owed to whoever took the address");
}
fn a_frame_address(readers: u32) -> (usize, Vec<mir::Inst>, Vec<(mir::Inst, u32)>) {
let (mut names, mut func, block) = empty();
let sp = mir::Reg::physical(RDI);
let address = func.new_vreg(GPR);
let lea = op(&mut names, FRAME.lea);
let load = op(&mut names, "mov_rm_32");
let local = func
.build(block, lea)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(sp, GPR)))
.finish();
for at in 0..readers {
let value = func.new_vreg(GPR);
func.build(block, load)
.def(value, GPR)
.mem(
mir::Mem::at(mir::Operand::read(address, GPR))
.plus(i32::try_from(at).unwrap_or(0) * 4),
)
.finish();
}
let (mut locals, mut arguments, mut growable) = (Vec::new(), vec![(local, 7)], Vec::new());
let mut pending =
Pending { addresses: &mut locals, arguments: &mut arguments, dynamic: &mut growable };
let folded = addresses(&mut func, &FRAME, &MACHINE, &mut names, &mut pending);
assert!(locals.is_empty(), "an argument is owed off the other list");
(folded, func.insts(block).collect(), arguments)
}
#[test]
fn an_address_into_the_frame_that_three_readers_take_is_owed_to_all_of_them() {
let (folded, left, owed) = a_frame_address(3);
assert_eq!(folded, 3);
assert_eq!(left.len(), 3, "the address is not its own instruction any more");
assert_eq!(owed, vec![(left[0], 7), (left[1], 7), (left[2], 7)]);
}
#[test]
fn an_address_into_the_frame_a_fourth_reader_wants_is_left_where_it_is() {
let (folded, left, owed) = a_frame_address(4);
assert_eq!(folded, 0);
assert_eq!(left.len(), 5, "the address and its four readers");
assert_eq!(owed, vec![(left[0], 7)], "the offset is still owed to the address itself");
}
#[test]
fn only_the_target_s_address_instruction_is_one_this_folds() {
let (mut names, mut func, block) = empty();
let array = func.new_vreg(GPR);
let address = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let load = op(&mut names, "mov_rm_64");
let read = op(&mut names, "mov_rm_32");
func.build(block, load)
.def(address, GPR)
.mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
.finish();
func.build(block, read)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
assert_eq!(folds(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
}