use std::collections::{HashMap, HashSet};
use rucc_base::Interner;
use rucc_mir as mir;
use rucc_target::{FrameInsts, Role};
pub fn addresses(
func: &mut mir::Func,
insts: &FrameInsts,
names: &mut Interner,
waiting: &HashSet<mir::Inst>,
) -> usize {
let lea = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.lea)));
let reads = reads(func);
let mut folded = 0;
for block in func.blocks().collect::<Vec<_>>() {
let mut open: HashMap<mir::Reg, mir::Inst> = HashMap::new();
for inst in func.insts(block).collect::<Vec<_>>() {
if let Some(folding) = candidate(func, &open, inst) {
let operands = func.push_operands(&folding.operands);
let mem = func.add_amode(folding.amode);
func[inst].operands = operands;
func[inst].mem = Some(mem);
open.remove(&folding.base);
func.remove_inst(folding.from);
folded += 1;
}
for written in written(func, inst) {
open.retain(|reg, &mut held| *reg != written && !touches(func, held, written));
}
if func[inst].opcode == lea && !waiting.contains(&inst) {
if let Some(reg) = written_once(func, &reads, inst) {
open.insert(reg, inst);
}
}
}
}
folded
}
pub(crate) fn reads(func: &mir::Func) -> HashMap<mir::Reg, usize> {
let mut counts = 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;
}
}
}
counts
}
fn written_once(
func: &mir::Func,
reads: &HashMap<mir::Reg, usize>,
inst: mir::Inst,
) -> Option<mir::Reg> {
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() || reads.get(&def.reg) != Some(&1) {
return None;
}
Some(def.reg)
}
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.got {
return None;
}
Some(func[func[inst].operands].get(usize::from(amode.base?))?.reg)
}
struct Folding {
from: mir::Inst,
base: mir::Reg,
operands: Vec<mir::Operand>,
amode: mir::Amode,
}
fn candidate(
func: &mir::Func,
open: &HashMap<mir::Reg, mir::Inst>,
inst: mir::Inst,
) -> Option<Folding> {
let base = base_reg(func, inst)?;
let from = *open.get(&base)?;
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 { from, base, operands, amode })
}
#[cfg(test)]
mod tests {
use rucc_target::x86_64::{FRAME, GPR, 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 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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_two_instructions_read_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 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 _ in 0..2 {
let value = func.new_vreg(GPR);
func.build(block, load)
.def(value, GPR)
.mem(mir::Mem::at(mir::Operand::read(address, GPR)))
.finish();
}
assert_eq!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 0);
assert_eq!(shape(&func, &names, block).len(), 3);
}
#[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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 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_is_left_where_it_is() {
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)))
.finish();
let waiting = HashSet::from([local]);
assert_eq!(addresses(&mut func, &FRAME, &mut names, &waiting), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
assert_eq!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 1);
}
#[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!(addresses(&mut func, &FRAME, &mut names, &HashSet::new()), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
}