use std::collections::HashMap;
use rucc_ir::{Block, BlockCall, Extra, ExtraKind, Func, Inst, InstData, Type, Value, ValueList};
pub(crate) fn blocks(
func: &mut Func,
body: &[Block],
map: &mut HashMap<Value, Value>,
) -> HashMap<Block, Block> {
let mut copies: HashMap<Block, Block> = HashMap::new();
for &block in body {
copies.insert(block, func.create_block());
}
for &block in body {
let copy = copies[&block];
for param in func[block].params.clone() {
if map.contains_key(¶m) {
continue;
}
let fresh = func.append_param(copy, func[param].ty);
map.insert(param, fresh);
}
}
let insts: Vec<(Block, Vec<Inst>)> =
body.iter().map(|&block| (block, func.insts(block).collect())).collect();
let mut copied: Vec<Inst> = Vec::new();
for (block, insts) in &insts {
let into = copies[block];
for &inst in insts {
copied.push(one(func, into, inst, map, &copies));
}
}
for inst in copied {
let args = func[inst].args;
func.rewrite(args, |value| map.get(&value).copied().unwrap_or(value));
let edges: Vec<ValueList> = func.successors(inst).map(|call| call.args).collect();
for edge in edges {
func.rewrite(edge, |value| map.get(&value).copied().unwrap_or(value));
}
}
copies
}
fn one(
func: &mut Func,
into: Block,
inst: Inst,
map: &mut HashMap<Value, Value>,
blocks: &HashMap<Block, Block>,
) -> Inst {
let data = func[inst];
let args: Vec<Value> = func[data.args].to_vec();
let edges: Vec<(Block, Vec<Value>)> = func
.successors(inst)
.map(|call| {
let block = blocks.get(&call.block).copied().unwrap_or(call.block);
(block, func[call.args].to_vec())
})
.collect();
let extra = match data.extra {
Extra::Targets(_) => {
let calls: Vec<BlockCall> = edges
.iter()
.map(|(block, args)| BlockCall { block: *block, args: func.push_values(args) })
.collect();
Extra::Targets(func.push_block_calls(&calls))
}
other => other,
};
let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
let span = func.span(inst);
let args = func.push_values(&args);
let fresh = func.create_inst(InstData { args, extra, ..data }, &types, span);
func.append_inst(into, fresh);
for (old, new) in data.results().zip(func[fresh].results()) {
map.insert(old, new);
}
fresh
}
pub(crate) fn copyable(func: &Func, inst: Inst) -> bool {
!matches!(func[inst].extra.kind(), ExtraKind::Switch | ExtraKind::Asm | ExtraKind::VaObject)
}
pub(crate) fn edge_args(func: &Func, term: Inst, to: Block) -> Vec<Value> {
for call in func.successors(term) {
if call.block == to {
return func[call.args].to_vec();
}
}
Vec::new()
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use rucc_base::Interner;
use rucc_ir::{
Builder, Flags, Func, IntPred, Module, Opcode, Signature, Type, Value, verify_func,
};
use rucc_target::{TargetInfo, Triple};
use super::blocks;
fn pair(names: &mut Interner) -> Func {
let i32_ = Type::int(32);
let sig = Signature::new().with_params(&[i32_]).with_returns(&[i32_]);
let mut func = Func::new(names.intern("pair"), sig);
let entry = func.create_block();
let arg = func.append_param(entry, i32_);
let body = func.create_block();
let held = func.append_param(body, i32_);
Builder::new(&mut func, entry).jump(body, &[arg]);
let mut build = Builder::new(&mut func, body);
let one = build.iconst(i32_, 1);
let sum = build.binary(Opcode::Add, held, one, Flags::NSW);
build.ret(&[sum]);
func
}
fn sound(func: &Func, names: &mut Interner) {
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let module = Module::new(names.intern("t.c"), &target);
if let Err(errors) = verify_func(&module, func, names) {
panic!("{errors:#?}");
}
}
#[test]
fn every_value_the_copy_defines_has_a_fresh_name_for_it() {
let mut names = Interner::new();
let mut func = pair(&mut names);
let body: Vec<_> = func.blocks().collect();
let defined: Vec<Value> = body
.iter()
.flat_map(|&block| {
let params = func[block].params.clone();
let results: Vec<Value> =
func.insts(block).flat_map(|inst| func[inst].results()).collect();
params.into_iter().chain(results)
})
.collect();
let mut map = HashMap::new();
let copies = blocks(&mut func, &body, &mut map);
assert_eq!(copies.len(), body.len());
for value in defined {
let fresh = map.get(&value).copied().expect("a name for everything the copy defines");
assert_ne!(fresh, value, "the copy is not the original");
}
}
#[test]
fn the_copy_goes_round_inside_itself_rather_than_back_to_the_original() {
let mut names = Interner::new();
let mut func = pair(&mut names);
let body: Vec<_> = func.blocks().collect();
let mut map = HashMap::new();
let copies = blocks(&mut func, &body, &mut map);
let entry = copies[&body[0]];
let term = func.terminator(entry).expect("the copied jump");
let targets: Vec<_> = func.successors(term).map(|call| call.block).collect();
assert_eq!(targets, vec![copies[&body[1]]]);
}
#[test]
fn a_parameter_the_caller_has_spoken_for_is_not_given_one_on_the_copy() {
let mut names = Interner::new();
let mut func = pair(&mut names);
let body: Vec<_> = func.blocks().collect();
let held = func[body[1]].params[0];
let seven = Builder::new(&mut func, body[0]).iconst(Type::int(32), 7);
let mut map = HashMap::from([(held, seven)]);
let copies = blocks(&mut func, &body, &mut map);
assert!(func[copies[&body[1]]].params.is_empty(), "spoken for, so no parameter of its own");
assert_eq!(map[&held], seven, "and it still reads what it was told to read");
let copy = copies[&body[1]];
let add = func.insts(copy).find(|&inst| func[inst].opcode == Opcode::Add).expect("the add");
assert!(func[func[add].args].contains(&seven), "including in the copy of the add");
}
#[test]
fn what_the_copy_reads_from_outside_it_is_left_alone() {
let mut names = Interner::new();
let mut func = pair(&mut names);
let body = vec![func.blocks().nth(1).expect("two blocks")];
let original = func[body[0]].params[0];
let mut map = HashMap::new();
let copies = blocks(&mut func, &body, &mut map);
let copy = copies[&body[0]];
let held = func[copy].params[0];
let add = func.insts(copy).find(|&inst| func[inst].opcode == Opcode::Add).expect("the add");
assert_ne!(held, original, "the copy has its own parameter");
assert!(func[func[add].args].contains(&held), "and the add in it reads that one");
}
#[test]
fn a_copied_loop_goes_round_itself_and_leaves_where_the_original_left() {
let mut names = Interner::new();
let i32_ = Type::int(32);
let sig = Signature::new().with_params(&[i32_]).with_returns(&[i32_]);
let mut func = Func::new(names.intern("count"), sig);
let entry = func.create_block();
let limit = func.append_param(entry, i32_);
let header = func.create_block();
let index = func.append_param(header, i32_);
let done = func.create_block();
let out = func.append_param(done, i32_);
let mut build = Builder::new(&mut func, entry);
let zero = build.iconst(i32_, 0);
build.jump(header, &[zero]);
let mut build = Builder::new(&mut func, header);
let one = build.iconst(i32_, 1);
let next = build.binary(Opcode::Add, index, one, Flags::NSW);
let test = build.icmp(IntPred::Slt, next, limit);
build.br_if(test, header, &[next], done, &[next]);
Builder::new(&mut func, done).ret(&[out]);
sound(&func, &mut names);
let body = vec![header];
let mut map = HashMap::new();
let copies = blocks(&mut func, &body, &mut map);
let copy = copies[&header];
let term = func.terminator(copy).expect("the copied test");
let targets: Vec<_> = func.successors(term).map(|call| call.block).collect();
assert_eq!(targets, vec![copy, done], "back to itself, out to where the original went");
let carried = func.successors(term).next().expect("the back edge").args;
assert_eq!(func[carried][0], map[&next], "and it carries its own value round");
}
}