use std::collections::HashMap;
use std::fmt;
use rucc_mir::{Block, Func, Inst, Operand, Param, Reg, Role};
use rucc_target::RegClass;
use crate::assign::{Assignment, Place};
use crate::rewrite::{At, Edit};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fault {
Read {
inst: Inst,
place: Place,
class: RegClass,
wanted: Reg,
found: Option<Reg>,
},
Arrived {
from: Block,
to: Block,
place: Place,
class: RegClass,
wanted: Reg,
found: Option<Reg>,
},
}
impl fmt::Display for Fault {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Fault::Read { inst, place, class, wanted, found } => write!(
f,
"instruction {} reads {} out of {}, which {}",
inst.index(),
name(*wanted),
spelled(*class, *place),
holding(*found)
),
Fault::Arrived { from, to, place, class, wanted, found } => write!(
f,
"the edge from block {} to block {} was to leave {} in {}, which {}",
from.index(),
to.index(),
name(*wanted),
spelled(*class, *place),
holding(*found)
),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Shape {
operands: Vec<Vec<Operand>>,
params: Vec<Vec<Param>>,
succs: Vec<Vec<Call>>,
}
#[derive(Debug, Clone)]
struct Call {
block: Block,
args: Vec<Reg>,
}
#[must_use]
pub fn shape(func: &Func) -> Shape {
let mut shape = Shape {
operands: vec![Vec::new(); func.inst_count()],
params: vec![Vec::new(); func.block_count()],
succs: vec![Vec::new(); func.block_count()],
};
for block in func.blocks() {
shape.params[block.index()] = func[block].params.clone();
shape.succs[block.index()] = func[block]
.succs
.iter()
.map(|call| Call { block: call.block, args: call.args.clone() })
.collect();
for inst in func.insts(block) {
shape.operands[inst.index()] = func[func[inst].operands].to_vec();
}
}
shape
}
#[must_use]
pub fn trace(func: &Func, shape: &Shape, assignment: &Assignment, edits: &[Edit]) -> Vec<Fault> {
let filed = File::of(func, edits);
let mut entry: Vec<Option<State>> = vec![None; func.block_count()];
let Some(start) = func.entry() else { return Vec::new() };
entry[start.index()] = Some(arrived(shape));
let mut queue = vec![start];
let mut ignored = Vec::new();
while let Some(block) = queue.pop() {
let Some(state) = entry[block.index()].clone() else { continue };
ignored.clear();
let out = body(func, shape, &filed, edits, block, state, &mut ignored);
let single = shape.succs[block.index()].len() == 1;
for call in &shape.succs[block.index()] {
let over =
cross(shape, &filed, edits, assignment, block, call, single, &out, &mut ignored);
if narrow(&mut entry[call.block.index()], &over) {
queue.push(call.block);
}
}
}
let mut faults = Vec::new();
for block in func.blocks() {
let Some(state) = entry[block.index()].clone() else { continue };
let out = body(func, shape, &filed, edits, block, state, &mut faults);
let single = shape.succs[block.index()].len() == 1;
for call in &shape.succs[block.index()] {
cross(shape, &filed, edits, assignment, block, call, single, &out, &mut faults);
}
}
faults
}
#[must_use]
pub fn report(faults: &[Fault]) -> String {
let places = if faults.len() == 1 { "place" } else { "places" };
let mut report = format!("the rewrite loses a value in {} {places}", faults.len());
for fault in faults {
report.push_str("\n ");
report.push_str(&fault.to_string());
}
report
}
fn arrived(shape: &Shape) -> State {
let mut state = State::new();
for operands in &shape.operands {
for operand in operands {
if operand.role == Role::Use && operand.reg.phys().is_some() {
state
.entry(spot(operand.class, Place::Reg(operand.reg.phys().expect("physical"))))
.or_insert(operand.reg);
}
}
}
state
}
type State = HashMap<Spot, Reg>;
type Spot = (u8, Place);
fn spot(class: RegClass, place: Place) -> Spot {
(class.number(), place)
}
#[derive(Debug, Default)]
struct File {
before: Vec<Vec<usize>>,
after: Vec<Vec<usize>>,
start_of: Vec<Vec<usize>>,
end_of: Vec<Vec<usize>>,
}
impl File {
fn of(func: &Func, edits: &[Edit]) -> Self {
let mut filed = File {
before: vec![Vec::new(); func.inst_count()],
after: vec![Vec::new(); func.inst_count()],
start_of: vec![Vec::new(); func.block_count()],
end_of: vec![Vec::new(); func.block_count()],
};
for (index, edit) in edits.iter().enumerate() {
match edit.at {
At::Before(inst) => filed.before[inst.index()].push(index),
At::After(inst) => filed.after[inst.index()].push(index),
At::StartOf(block) => filed.start_of[block.index()].push(index),
At::EndOf(block) => filed.end_of[block.index()].push(index),
}
}
filed
}
}
fn body(
func: &Func,
shape: &Shape,
filed: &File,
edits: &[Edit],
block: Block,
mut state: State,
faults: &mut Vec<Fault>,
) -> State {
for inst in func.insts(block) {
for &edit in &filed.before[inst.index()] {
moved(&mut state, &edits[edit]);
}
let was = &shape.operands[inst.index()];
let now = &func[func[inst].operands];
for (operand, place) in was.iter().zip(now.iter()) {
let Some(at) = landed(place) else { continue };
if operand.role != Role::Use {
continue;
}
let found = state.get(&spot(operand.class, at)).copied();
if found != Some(operand.reg) {
let (class, wanted) = (operand.class, operand.reg);
faults.push(Fault::Read { inst, place: at, class, wanted, found });
}
}
for (operand, place) in was.iter().zip(now.iter()) {
let Some(at) = landed(place) else { continue };
if !operand.role.is_def() {
continue;
}
state.insert(spot(operand.class, at), operand.reg);
}
for &edit in &filed.after[inst.index()] {
moved(&mut state, &edits[edit]);
}
}
state
}
#[allow(clippy::too_many_arguments, reason = "an edge is the two blocks and everything between")]
fn cross(
shape: &Shape,
filed: &File,
edits: &[Edit],
assignment: &Assignment,
from: Block,
call: &Call,
single: bool,
out: &State,
faults: &mut Vec<Fault>,
) -> State {
let mut state = out.clone();
let params = &shape.params[call.block.index()];
let list =
if single { &filed.end_of[from.index()] } else { &filed.start_of[call.block.index()] };
for &edit in list {
moved(&mut state, &edits[edit]);
}
let mut arrived = Vec::new();
for (param, &arg) in params.iter().zip(&call.args) {
let Some(at) = home(assignment, param.reg) else { continue };
let found = state.get(&spot(param.class, at)).copied();
if found != Some(arg) {
let (to, class) = (call.block, param.class);
faults.push(Fault::Arrived { from, to, place: at, class, wanted: arg, found });
}
arrived.push((spot(param.class, at), param.reg));
}
for (spot, reg) in arrived {
state.insert(spot, reg);
}
state
}
fn moved(state: &mut State, edit: &Edit) {
let to = spot(edit.class, edit.mov.to);
let from = spot(edit.class, edit.mov.from);
match state.get(&from).copied() {
Some(reg) => state.insert(to, reg),
None => state.remove(&to),
};
}
fn narrow(entry: &mut Option<State>, over: &State) -> bool {
match entry {
None => {
*entry = Some(over.clone());
true
}
Some(state) => {
let before = state.len();
state.retain(|spot, reg| over.get(spot) == Some(&*reg));
state.len() != before
}
}
}
fn landed(operand: &Operand) -> Option<Place> {
operand.reg.phys().map(Place::Reg)
}
fn home(assignment: &Assignment, reg: Reg) -> Option<Place> {
assignment.place(reg).or_else(|| reg.phys().map(Place::Reg))
}
fn name(reg: Reg) -> String {
match reg.number() {
Some(number) => format!("%{number}"),
None => match reg.phys() {
Some(at) => format!("register {}", at.number()),
None => "nothing".to_owned(),
},
}
}
fn spelled(class: RegClass, place: Place) -> String {
match place {
Place::Reg(at) => format!("register {} of class {}", at.number(), class.number()),
Place::Slot(slot) => format!("slot {slot}"),
}
}
fn holding(found: Option<Reg>) -> String {
match found {
Some(reg) => format!("holds {}", name(reg)),
None => "holds nothing anything has put there".to_owned(),
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::{BlockCall, Constraint, Opcode, Operand};
use rucc_target::x86_64::{GPR, RAX, RSP, SYSV};
use super::*;
use crate::assign::{Env, assign};
use crate::live::Live;
use crate::moves::Move;
use crate::order::Order;
use crate::rewrite::rewrite;
fn env() -> Env {
let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
Env::new().with(GPR, order, scratch)
}
fn narrow(count: usize) -> Env {
Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 2])
}
fn allocate(func: &mut Func, env: &Env) -> (Shape, Assignment, Vec<Edit>) {
let order = Order::of(func);
let live = Live::of(func, &order);
let mut assignment = assign(func, &order, &live, env);
let taken = shape(func);
let edits = rewrite(func, &mut assignment, env);
(taken, assignment, edits)
}
fn said(func: &Func, taken: &Shape, assignment: &Assignment, edits: &[Edit]) -> Vec<String> {
trace(func, taken, assignment, edits).iter().map(ToString::to_string).collect()
}
#[test]
fn every_value_an_instruction_reads_is_the_one_that_was_written_where_it_reads_it() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
func.build(block, opcode).def(first, GPR).finish();
func.build(block, opcode).def(second, GPR).finish();
func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
let (taken, assignment, edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
}
#[test]
fn a_value_that_lives_on_the_stack_is_followed_through_the_slot_it_lives_in() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
let third = func.new_vreg(GPR);
func.build(block, opcode).def(first, GPR).finish();
func.build(block, opcode).def(second, GPR).finish();
func.build(block, opcode).def(third, GPR).finish();
func.build(block, opcode).uses(first, GPR).uses(second, GPR).uses(third, GPR).finish();
let (taken, assignment, edits) = allocate(&mut func, &narrow(2));
assert_eq!(assignment.spilled(), 1);
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
}
#[test]
fn an_operand_the_rewrite_pointed_at_the_wrong_register_is_reported() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
let read = {
func.build(block, opcode).def(first, GPR).finish();
func.build(block, opcode).def(second, GPR).finish();
func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish()
};
let (taken, assignment, edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
let list = func[read].operands;
func[list][0].reg = func[list][1].reg;
assert_eq!(
said(&func, &taken, &assignment, &edits),
["instruction 2 reads %0 out of register 1 of class 0, which holds %1"]
);
}
#[test]
fn a_move_that_writes_the_wrong_register_is_an_instruction_reading_the_wrong_value() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
let dividend = func.new_vreg(GPR);
let quotient = func.new_vreg(GPR);
func.build(block, opcode).def(dividend, GPR).finish();
func.build(block, opcode)
.operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
.operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
.finish();
func.build(block, opcode).uses(quotient, GPR).finish();
func.build(block, opcode).uses(dividend, GPR).finish();
let (taken, assignment, mut edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
edits[0].mov.to = Place::Reg(SYSV.int_order[2]);
assert_eq!(
said(&func, &taken, &assignment, &edits),
["instruction 1 reads %0 out of register 0 of class 0, which holds nothing anything \
has put there"]
);
}
#[test]
fn a_value_carried_over_an_edge_goes_on_under_the_name_the_block_it_arrives_in_gives_it() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let head = func.create_block();
let tail = func.create_block();
let value = func.new_vreg(GPR);
func.build(head, opcode).def(value, GPR).finish();
let arrived = func.append_param(tail, GPR);
*func.succs_mut(head) = vec![BlockCall::with(tail, vec![value])];
func.build(tail, opcode).uses(arrived, GPR).finish();
let (taken, assignment, edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
}
#[test]
fn two_values_that_swap_on_an_edge_arrive_the_right_way_round_only_in_the_order_they_were_put_in()
{
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let head = func.create_block();
let body = func.create_block();
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
func.build(head, opcode).def(first, GPR).finish();
func.build(head, opcode).def(second, GPR).finish();
let left = func.append_param(body, GPR);
let right = func.append_param(body, GPR);
*func.succs_mut(head) = vec![BlockCall::with(body, vec![first, second])];
func.build(body, opcode).uses(left, GPR).uses(right, GPR).finish();
*func.succs_mut(body) = vec![BlockCall::with(body, vec![right, left])];
let (taken, assignment, mut edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
let (to, from) = (Place::Reg(SYSV.int_order[0]), Place::Reg(SYSV.int_order[1]));
edits.truncate(edits.len() - 3);
edits.push(Edit { at: At::EndOf(body), mov: Move::new(to, from), class: GPR });
edits.push(Edit { at: At::EndOf(body), mov: Move::new(from, to), class: GPR });
assert_eq!(
said(&func, &taken, &assignment, &edits),
["the edge from block 1 to block 1 was to leave %2 in register 1 of class 0, which \
holds %3"]
);
}
#[test]
fn a_loop_is_walked_until_it_settles_rather_than_reported_the_first_time_round() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let head = func.create_block();
let body = func.create_block();
let latch = func.create_block();
let out = func.create_block();
let start = func.new_vreg(GPR);
func.build(head, opcode).def(start, GPR).finish();
let counter = func.append_param(body, GPR);
*func.succs_mut(head) = vec![BlockCall::with(body, vec![start])];
let next = func.new_vreg(GPR);
func.build(body, opcode).def(next, GPR).uses(counter, GPR).finish();
*func.succs_mut(body) = vec![BlockCall::to(latch), BlockCall::to(out)];
func.build(latch, opcode).finish();
*func.succs_mut(latch) = vec![BlockCall::with(body, vec![next])];
func.build(out, opcode).finish();
let (taken, assignment, edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
}
#[test]
fn a_value_the_two_ways_into_a_block_leave_in_different_places_is_not_one_it_may_read() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let entry = func.create_block();
let arm = func.create_block();
let tail = func.create_block();
let value = func.new_vreg(GPR);
func.build(entry, opcode).def(value, GPR).finish();
*func.succs_mut(entry) = vec![BlockCall::to(arm), BlockCall::to(tail)];
func.build(arm, opcode).finish();
*func.succs_mut(arm) = vec![BlockCall::to(tail)];
func.build(tail, opcode).uses(value, GPR).finish();
let (taken, assignment, mut edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
let at = Place::Reg(SYSV.int_order[0]);
let elsewhere = Place::Reg(SYSV.int_order[1]);
edits.push(Edit { at: At::EndOf(arm), mov: Move::new(at, elsewhere), class: GPR });
assert_eq!(
said(&func, &taken, &assignment, &edits),
["instruction 2 reads %0 out of register 0 of class 0, which holds nothing anything \
has put there"]
);
}
#[test]
fn a_register_the_function_arrives_holding_is_one_it_may_read_without_writing_it_first() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
func.build(block, opcode).operand(Operand::read(Reg::physical(RSP), GPR)).finish();
let (taken, assignment, edits) = allocate(&mut func, &env());
assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
}
#[test]
fn what_is_wrong_is_reported_in_a_sentence_that_says_how_many_things_are_wrong() {
let fault = Fault::Read {
inst: Inst::new(3),
place: Place::Slot(1),
class: GPR,
wanted: Reg::virtual_reg(2),
found: None,
};
assert_eq!(
report(&[fault]),
"the rewrite loses a value in 1 place\n instruction 3 reads %2 out of slot 1, which \
holds nothing anything has put there"
);
}
}