use crate::cursor::{Cursor, CursorPosition, EncCursor, FuncCursor};
use crate::entity::EntitySet;
use crate::flowgraph::ControlFlowGraph;
use crate::ir::dfg::DataFlowGraph;
use crate::ir::instructions::BranchInfo;
use crate::ir::stackslot::{StackSlotKind, StackSlots};
use crate::ir::{
Ebb, Function, Inst, InstBuilder, InstructionData, Opcode, StackSlotData, Type, Value, ValueLoc,
};
use crate::isa::{RegInfo, RegUnit, TargetIsa};
use crate::regalloc::RegDiversions;
use core::convert::TryInto;
use cranelift_entity::{PrimaryMap, SecondaryMap};
use std::vec::Vec;
#[derive(Clone, PartialEq)]
enum ZeroOneOrMany {
Zero,
One,
Many,
}
#[derive(Clone, Copy)]
struct SlotInfo {
kind: StackSlotKind,
offset: i32,
size: u32,
}
#[derive(Clone)]
struct AvailEnv {
map: Vec<Option<SlotInfo>>,
}
struct ProcessingStackElem {
avail_env: AvailEnv,
cursor: CursorPosition,
diversions: RegDiversions,
}
pub struct RedundantReloadRemover {
num_regunits: Option<u16>,
num_preds_per_ebb: PrimaryMap<Ebb, ZeroOneOrMany>,
discovery_stack: Vec<Ebb>,
nodes_in_tree: EntitySet<Ebb>,
processing_stack: Vec<ProcessingStackElem>,
nodes_already_visited: EntitySet<Ebb>,
}
fn is_slot_kind_tracked(kind: StackSlotKind) -> bool {
match kind {
StackSlotKind::SpillSlot | StackSlotKind::IncomingArg => true,
_ => false,
}
}
fn overlaps(si: &SlotInfo, offset: i32, size: u32) -> bool {
let a_offset = si.offset as i64;
let a_size = si.size as i64;
let b_offset = offset as i64;
let b_size = size as i64;
let no_overlap = a_offset + a_size <= b_offset || b_offset + b_size <= a_offset;
!no_overlap
}
fn find_bank_limits(reginfo: &RegInfo, reg: RegUnit) -> (RegUnit, u16) {
if let Some(bank) = reginfo.bank_containing_regunit(reg) {
return (bank.first_unit, bank.units);
}
panic!("find_regclass_limits: reg not found");
}
fn reg_of_value(locations: &SecondaryMap<Value, ValueLoc>, v: Value) -> RegUnit {
match locations[v] {
ValueLoc::Reg(ru) => ru,
_ => panic!("reg_of_value: value isn't in a reg"),
}
}
fn slot_of_value<'s>(
locations: &SecondaryMap<Value, ValueLoc>,
stack_slots: &'s StackSlots,
v: Value,
) -> &'s StackSlotData {
match locations[v] {
ValueLoc::Stack(slot) => &stack_slots[slot],
_ => panic!("slot_of_value: value isn't in a stack slot"),
}
}
impl RedundantReloadRemover {
fn discovery_stack_push_successors_of(&mut self, cfg: &ControlFlowGraph, node: Ebb) {
for successor in cfg.succ_iter(node) {
self.discovery_stack.push(successor);
}
}
fn add_nodes_to_tree(&mut self, cfg: &ControlFlowGraph, starting_point: Ebb) {
debug_assert!(self.discovery_stack.is_empty());
debug_assert!(self.nodes_in_tree.is_empty());
self.nodes_in_tree.insert(starting_point);
self.discovery_stack_push_successors_of(cfg, starting_point);
while let Some(node) = self.discovery_stack.pop() {
match self.num_preds_per_ebb[node] {
ZeroOneOrMany::Many => {}
ZeroOneOrMany::One => {
self.nodes_in_tree.insert(node);
self.discovery_stack_push_successors_of(cfg, node);
}
ZeroOneOrMany::Zero => panic!("add_nodes_to_tree: inconsistent graph"),
}
}
}
}
impl AvailEnv {
fn new(size: usize) -> Self {
let mut env = AvailEnv {
map: Vec::<Option<SlotInfo>>::new(),
};
env.map.resize(size, None);
env
}
#[cfg(debug_assertions)]
fn check_invariants(&self) -> bool {
for i in 0..self.map.len() {
if let Some(si) = self.map[i] {
for j in i + 1..self.map.len() {
if let Some(sj) = self.map[j] {
if si.kind == sj.kind
&& overlaps(&si, sj.offset, sj.size)
&& !(si.offset == sj.offset && si.size == sj.size)
{
return false;
}
}
}
}
}
true
}
fn invalidate_by_reg(&mut self, reg: RegUnit) {
self.map[reg as usize] = None;
}
fn invalidate_by_offset(&mut self, kind: StackSlotKind, offset: i32, size: u32) {
debug_assert!(is_slot_kind_tracked(kind));
for i in 0..self.map.len() {
if let Some(si) = &self.map[i] {
if si.kind == kind && overlaps(&si, offset, size) {
self.map[i] = None;
}
}
}
}
fn invalidate_all(&mut self) {
for i in 0..self.map.len() {
self.map[i] = None;
}
}
fn copy_reg(&mut self, src: RegUnit, dst: RegUnit) {
self.map[dst as usize] = self.map[src as usize];
}
fn has_exact_binding(&self, reg: RegUnit, kind: StackSlotKind, offset: i32, size: u32) -> bool {
debug_assert!(is_slot_kind_tracked(kind));
if let Some(si) = &self.map[reg as usize] {
return si.kind == kind && si.offset == offset && si.size == size;
}
false
}
fn has_inexact_binding(
&self,
reginfo: &RegInfo,
reg: RegUnit,
kind: StackSlotKind,
offset: i32,
size: u32,
) -> Option<RegUnit> {
debug_assert!(is_slot_kind_tracked(kind));
let (first_unit, num_units) = find_bank_limits(reginfo, reg);
for other_reg in first_unit..first_unit + num_units {
if let Some(si) = &self.map[other_reg as usize] {
if si.kind == kind && si.offset == offset && si.size == size {
if other_reg == reg {
panic!("has_inexact_binding: binding *is* exact!");
}
return Some(other_reg);
}
}
}
None
}
fn bind(&mut self, reg: RegUnit, kind: StackSlotKind, offset: i32, size: u32) {
debug_assert!(is_slot_kind_tracked(kind));
self.invalidate_by_offset(kind, offset, size);
self.map[reg as usize] = Some(SlotInfo { kind, offset, size });
}
}
fn invalidate_regs_written_by_inst(
locations: &SecondaryMap<Value, ValueLoc>,
diversions: &RegDiversions,
dfg: &DataFlowGraph,
avail_env: &mut AvailEnv,
inst: Inst,
) {
for v in dfg.inst_results(inst).iter() {
if let ValueLoc::Reg(ru) = locations[*v] {
debug_assert!(diversions.reg(*v, locations) == ru);
avail_env.invalidate_by_reg(ru);
}
}
}
impl RedundantReloadRemover {
fn visit_inst(
&mut self,
func: &mut Function,
reginfo: &RegInfo,
isa: &dyn TargetIsa,
inst: Inst,
) {
debug_assert!(!self.processing_stack.is_empty());
let ProcessingStackElem {
avail_env,
cursor: _,
diversions,
} = &mut self.processing_stack.last_mut().unwrap();
#[cfg(debug_assertions)]
debug_assert!(
avail_env.check_invariants(),
"visit_inst: env invariants not ok"
);
let dfg = &mut func.dfg;
let locations = &func.locations;
let stack_slots = &func.stack_slots;
enum Transform {
NoChange,
ChangeToNopFill(Value), ChangeToCopyToSSA(Type, RegUnit), }
let mut transform = Transform::NoChange;
match &dfg[inst] {
InstructionData::Unary {
opcode: Opcode::Spill,
arg: src_value,
} => {
let slot = slot_of_value(locations, stack_slots, dfg.inst_results(inst)[0]);
let src_reg = diversions.reg(*src_value, locations);
let kind = slot.kind;
if is_slot_kind_tracked(kind) {
let offset = slot.offset.expect("visit_inst: spill with no offset");
let size = slot.size;
avail_env.bind(src_reg, kind, offset, size);
} else {
invalidate_regs_written_by_inst(locations, diversions, dfg, avail_env, inst);
}
}
InstructionData::Unary {
opcode: Opcode::Fill,
arg: src_value,
} => {
let slot = slot_of_value(locations, stack_slots, *src_value);
let dst_value = dfg.inst_results(inst)[0];
let dst_reg = reg_of_value(locations, dst_value);
debug_assert!(dst_reg == diversions.reg(dst_value, locations));
let kind = slot.kind;
if is_slot_kind_tracked(kind) {
let offset = slot.offset.expect("visit_inst: fill with no offset");
let size = slot.size;
if avail_env.has_exact_binding(dst_reg, kind, offset, size) {
transform = Transform::ChangeToNopFill(*src_value);
} else if let Some(other_reg) =
avail_env.has_inexact_binding(reginfo, dst_reg, kind, offset, size)
{
debug_assert!(other_reg != dst_reg);
transform =
Transform::ChangeToCopyToSSA(dfg.value_type(dst_value), other_reg);
avail_env.copy_reg(other_reg, dst_reg);
} else {
avail_env.bind(dst_reg, kind, offset, size);
}
} else {
invalidate_regs_written_by_inst(locations, diversions, dfg, avail_env, inst);
}
}
InstructionData::RegMove {
opcode: _,
arg: _,
src,
dst,
} => {
avail_env.copy_reg(*src, *dst);
}
InstructionData::RegSpill { .. }
| InstructionData::RegFill { .. }
| InstructionData::Call { .. }
| InstructionData::CallIndirect { .. }
| InstructionData::StackLoad { .. }
| InstructionData::StackStore { .. }
| InstructionData::Unary {
opcode: Opcode::AdjustSpDown,
..
}
| InstructionData::UnaryImm {
opcode: Opcode::AdjustSpUpImm,
..
}
| InstructionData::UnaryImm {
opcode: Opcode::AdjustSpDownImm,
..
} => {
avail_env.invalidate_all();
}
_ => {
invalidate_regs_written_by_inst(locations, diversions, dfg, avail_env, inst);
}
}
match transform {
Transform::NoChange => {}
Transform::ChangeToNopFill(arg) => {
dfg.replace(inst).fill_nop(arg);
let ok = func.update_encoding(inst, isa).is_ok();
debug_assert!(ok, "fill_nop encoding missing for this type");
}
Transform::ChangeToCopyToSSA(ty, reg) => {
dfg.replace(inst).copy_to_ssa(ty, reg);
let ok = func.update_encoding(inst, isa).is_ok();
debug_assert!(ok, "copy_to_ssa encoding missing for type {}", ty);
}
}
}
}
impl RedundantReloadRemover {
fn processing_stack_push(&mut self, cursor: CursorPosition) {
let avail_env = if let Some(stack_top) = self.processing_stack.last() {
stack_top.avail_env.clone()
} else {
AvailEnv::new(
self.num_regunits
.expect("processing_stack_push: num_regunits unknown!")
as usize,
)
};
self.processing_stack.push(ProcessingStackElem {
avail_env,
cursor,
diversions: RegDiversions::new(),
});
}
fn processing_stack_maybe_push(&mut self, dst: Ebb) {
if self.nodes_in_tree.contains(dst) && !self.nodes_already_visited.contains(dst) {
if !self.processing_stack.is_empty() {
debug_assert!(self.num_preds_per_ebb[dst] == ZeroOneOrMany::One);
}
self.processing_stack_push(CursorPosition::Before(dst));
self.nodes_already_visited.insert(dst);
}
}
fn process_tree(
&mut self,
func: &mut Function,
reginfo: &RegInfo,
isa: &dyn TargetIsa,
root: Ebb,
) {
debug_assert!(self.nodes_in_tree.contains(root));
debug_assert!(self.processing_stack.is_empty());
debug_assert!(self.nodes_already_visited.is_empty());
self.processing_stack_maybe_push(root);
while !self.processing_stack.is_empty() {
let tos = self.processing_stack.len() - 1;
let mut pos = FuncCursor::new(func).at_position(self.processing_stack[tos].cursor);
let maybe_inst = pos.next_inst();
self.processing_stack[tos].cursor = pos.position();
if let Some(inst) = maybe_inst {
self.visit_inst(func, reginfo, isa, inst);
self.processing_stack[tos].diversions.apply(&func.dfg[inst]);
match func.dfg.analyze_branch(inst) {
BranchInfo::NotABranch => (),
BranchInfo::SingleDest(dst, _) => {
self.processing_stack_maybe_push(dst);
}
BranchInfo::Table(jt, default) => {
func.jump_tables[jt]
.iter()
.for_each(|dst| self.processing_stack_maybe_push(*dst));
if let Some(dst) = default {
self.processing_stack_maybe_push(dst);
}
}
}
} else {
self.processing_stack.pop();
}
}
}
}
impl RedundantReloadRemover {
pub fn new() -> Self {
Self {
num_regunits: None,
num_preds_per_ebb: PrimaryMap::<Ebb, ZeroOneOrMany>::with_capacity(8),
discovery_stack: Vec::<Ebb>::with_capacity(16),
nodes_in_tree: EntitySet::<Ebb>::new(),
processing_stack: Vec::<ProcessingStackElem>::with_capacity(8),
nodes_already_visited: EntitySet::<Ebb>::new(),
}
}
pub fn clear(&mut self) {
self.clear_for_new_function();
}
fn clear_for_new_function(&mut self) {
self.num_preds_per_ebb.clear();
self.clear_for_new_tree();
}
fn clear_for_new_tree(&mut self) {
self.discovery_stack.clear();
self.nodes_in_tree.clear();
self.processing_stack.clear();
self.nodes_already_visited.clear();
}
#[inline(never)]
fn do_redundant_fill_removal_on_function(
&mut self,
func: &mut Function,
reginfo: &RegInfo,
isa: &dyn TargetIsa,
cfg: &ControlFlowGraph,
) {
let num_ebbs: u32 = func.dfg.num_ebbs().try_into().unwrap();
self.clear_for_new_function();
self.num_preds_per_ebb.clear();
self.num_preds_per_ebb.reserve(num_ebbs as usize);
for i in 0..num_ebbs {
let mut pi = cfg.pred_iter(Ebb::from_u32(i));
let mut n_pi = ZeroOneOrMany::Zero;
if let Some(_) = pi.next() {
n_pi = ZeroOneOrMany::One;
if let Some(_) = pi.next() {
n_pi = ZeroOneOrMany::Many;
}
}
self.num_preds_per_ebb.push(n_pi);
}
debug_assert!(self.num_preds_per_ebb.len() == num_ebbs as usize);
let entry_ebb = func
.layout
.entry_block()
.expect("do_redundant_fill_removal_on_function: entry ebb unknown");
debug_assert!(self.num_preds_per_ebb[entry_ebb] == ZeroOneOrMany::Zero);
self.num_preds_per_ebb[entry_ebb] = ZeroOneOrMany::Many;
for root_ix in 0..self.num_preds_per_ebb.len() {
let root = Ebb::from_u32(root_ix as u32);
if self.num_preds_per_ebb[root] != ZeroOneOrMany::Many {
continue;
}
self.clear_for_new_tree();
self.add_nodes_to_tree(cfg, root);
debug_assert!(self.nodes_in_tree.cardinality() > 0);
debug_assert!(self.num_preds_per_ebb[root] == ZeroOneOrMany::Many);
self.process_tree(func, reginfo, isa, root);
debug_assert!(
self.nodes_in_tree.cardinality() == self.nodes_already_visited.cardinality()
);
}
}
}
struct Context<'a> {
cur: EncCursor<'a>,
reginfo: RegInfo,
cfg: &'a ControlFlowGraph,
state: &'a mut RedundantReloadRemover,
}
impl RedundantReloadRemover {
pub fn run(&mut self, isa: &dyn TargetIsa, func: &mut Function, cfg: &ControlFlowGraph) {
let ctx = Context {
cur: EncCursor::new(func, isa),
reginfo: isa.register_info(),
cfg: cfg,
state: &mut RedundantReloadRemover::new(),
};
let mut total_regunits = 0;
for rb in isa.register_info().banks {
total_regunits += rb.units;
}
ctx.state.num_regunits = Some(total_regunits);
ctx.state.do_redundant_fill_removal_on_function(
ctx.cur.func,
&ctx.reginfo,
ctx.cur.isa,
&ctx.cfg,
);
}
}