use crate::backend::bitset::Bitset;
use crate::backend::cross_block_ids::{CrossBlockId, CrossBlockValues};
use crate::backend::dense_live_set::DenseLiveSet;
use crate::backend::treeify::Trees;
use crate::cfg::CFGInfo;
use crate::entity::{EntityRef, EntityVec, PerEntity};
use crate::ir::{Block, FunctionBody, Local, Type, Value, ValueDef};
use fxhash::FxHashMap as HashMap;
use smallvec::{smallvec, SmallVec};
use std::ops::Range;
#[derive(Clone, Debug, Default)]
pub struct Localifier {
pub values: PerEntity<Value, SmallVec<[Local; 2]>>,
pub locals: EntityVec<Local, Type>,
}
impl Localifier {
pub fn compute(body: &FunctionBody, cfg: &CFGInfo, trees: &Trees) -> Self {
Context::new(body, cfg, trees).compute()
}
}
struct Context<'a> {
body: &'a FunctionBody,
cfg: &'a CFGInfo,
trees: &'a Trees,
results: Localifier,
cross_blocks: CrossBlockValues,
block_end_live: PerEntity<Block, Bitset>,
ranges: Vec<Range<usize>>,
points: usize,
}
trait Visitor {
fn visit_use(&mut self, _: Value) {}
fn visit_def(&mut self, _: Value) {}
fn post_inst(&mut self, _: Value) {}
fn pre_inst(&mut self, _: Value) {}
fn post_term(&mut self) {}
fn pre_term(&mut self) {}
fn post_params(&mut self) {}
fn pre_params(&mut self) {}
}
fn is_tree_or_remat(value: Value, trees: &Trees) -> bool {
trees.owner.contains_key(&value) || trees.remat.contains(&value)
}
struct BlockVisitor<'a, V: Visitor> {
body: &'a FunctionBody,
trees: &'a Trees,
visitor: V,
}
impl<'a, V: Visitor> BlockVisitor<'a, V> {
fn new(body: &'a FunctionBody, trees: &'a Trees, visitor: V) -> Self {
log::trace!(
"localify: running on:\n{}",
body.display_verbose("| ", None)
);
Self {
body,
trees,
visitor,
}
}
fn visit_block(&mut self, block: Block) {
self.visitor.post_term();
self.body.blocks[block].terminator.visit_uses(|u| {
self.visit_use(u);
});
self.visitor.pre_term();
for &inst in self.body.blocks[block].insts.iter().rev() {
if is_tree_or_remat(inst, self.trees) {
continue;
}
self.visitor.post_inst(inst);
self.visit_inst(inst, true);
self.visitor.pre_inst(inst);
}
self.visitor.post_params();
for &(_, param) in &self.body.blocks[block].params {
self.visitor.visit_def(param);
}
self.visitor.pre_params();
}
fn visit_inst(&mut self, value: Value, root: bool) {
if let ValueDef::Operator(_, args, _) = &self.body.values[value] {
if root {
self.visitor.visit_def(value);
}
for &arg in &self.body.arg_pool[*args] {
self.visit_use(arg);
}
}
}
fn visit_use(&mut self, value: Value) {
let value = self.body.resolve_alias(value);
if let ValueDef::PickOutput(value, _, _) = self.body.values[value] {
self.visit_use(value);
return;
}
if is_tree_or_remat(value, self.trees) {
self.visit_inst(value, false);
} else {
self.visitor.visit_use(value);
}
}
}
impl<'a> Context<'a> {
fn new(body: &'a FunctionBody, cfg: &'a CFGInfo, trees: &'a Trees) -> Self {
let mut results = Localifier::default();
for &(ty, value) in &body.blocks[body.entry].params {
let param_local = results.locals.push(ty);
results.values[value] = smallvec![param_local];
}
Self {
body,
cfg,
trees,
results,
cross_blocks: CrossBlockValues::default(),
block_end_live: PerEntity::default(),
ranges: vec![usize::MAX..usize::MAX; body.values.len()],
points: 0,
}
}
fn find_cross_block_values(&mut self) {
self.cross_blocks = CrossBlockValues::build(self.body, self.trees);
}
fn compute_liveness(&mut self) {
struct LivenessVisitor<'b> {
cross_blocks: &'b CrossBlockValues,
live: DenseLiveSet,
}
impl<'b> Visitor for LivenessVisitor<'b> {
fn visit_use(&mut self, value: Value) {
if let Some(id) = self.cross_blocks.get(value) {
self.live.set_live(id.as_u32());
}
}
fn visit_def(&mut self, value: Value) {
if let Some(id) = self.cross_blocks.get(value) {
self.live.set_dead(id.as_u32());
}
}
}
let mut visitor = BlockVisitor::new(
self.body,
self.trees,
LivenessVisitor {
cross_blocks: &self.cross_blocks,
live: DenseLiveSet::new(self.cross_blocks.len()),
},
);
let mut workqueue: Vec<Block> = self.cfg.rpo.values().cloned().collect();
let mut workqueue_set = vec![true; self.body.blocks.len()];
while let Some(block) = workqueue.pop() {
workqueue_set[block.index()] = false;
visitor.visitor.live.next_epoch();
for id in self.block_end_live[block].iter() {
visitor.visitor.live.set_live(id);
}
visitor.visit_block(block);
let live = &visitor.visitor.live;
for &pred in &self.body.blocks[block].preds {
let pred_live = &mut self.block_end_live[pred];
let mut changed = false;
for &id in live.touched() {
if live.is_live(id) && pred_live.insert(id) {
changed = true;
}
}
if changed && !workqueue_set[pred.index()] {
workqueue_set[pred.index()] = true;
workqueue.push(pred);
}
}
}
}
fn find_ranges(&mut self) {
let mut point = 0;
struct LiveRangeVisitor<'b> {
point: &'b mut usize,
live: DenseLiveSet,
start: &'b mut [usize],
ranges: &'b mut [Range<usize>],
}
impl<'b> LiveRangeVisitor<'b> {
fn record_def(&mut self, value: Value, range: Range<usize>) {
let existing = &mut self.ranges[value.index()];
if existing.start == usize::MAX {
*existing = range;
} else {
existing.start = std::cmp::min(existing.start, range.start);
existing.end = std::cmp::max(existing.end, range.end);
}
}
}
impl<'b> Visitor for LiveRangeVisitor<'b> {
fn pre_params(&mut self) {
*self.point += 1;
}
fn pre_inst(&mut self, _: Value) {
*self.point += 1;
}
fn pre_term(&mut self) {
*self.point += 1;
}
fn visit_use(&mut self, value: Value) {
if self.live.set_live(value.index() as u32) {
self.start[value.index()] = *self.point;
}
}
fn visit_def(&mut self, value: Value) {
let range = if self.live.set_dead(value.index() as u32) {
self.start[value.index()]..(*self.point + 1)
} else {
*self.point..(*self.point + 1)
};
self.record_def(value, range);
}
}
let mut start = vec![0usize; self.body.values.len()];
let mut live = DenseLiveSet::new(self.body.values.len());
for &block in self.cfg.rpo.values().rev() {
live.next_epoch();
let visitor = LiveRangeVisitor {
live,
start: &mut start,
point: &mut point,
ranges: &mut self.ranges,
};
let mut visitor = BlockVisitor::new(&self.body, self.trees, visitor);
for id in self.block_end_live[block].iter() {
let livein = self.cross_blocks.value(CrossBlockId::new(id));
let livein = self.body.resolve_alias(livein);
visitor.visitor.visit_use(livein);
}
visitor.visit_block(block);
let still_live: Vec<Value> = visitor
.visitor
.live
.touched()
.iter()
.copied()
.filter(|&i| visitor.visitor.live.is_live(i))
.map(|i| Value::new(i as usize))
.collect();
for v in still_live {
visitor.visitor.visit_def(v);
}
live = visitor.visitor.live;
}
self.points = point + 1;
}
fn allocate(&mut self) {
let mut ranges: Vec<(Value, std::ops::Range<usize>)> = self
.ranges
.iter()
.enumerate()
.filter(|(_, r)| r.start != usize::MAX)
.map(|(i, r)| (Value::new(i), r.clone()))
.collect();
ranges.sort_unstable_by_key(|(val, range)| (range.start, *val));
let mut expiring: HashMap<usize, SmallVec<[(Type, Local); 8]>> = HashMap::default();
let mut range_idx = 0;
let mut freelist: HashMap<Type, Vec<Local>> = HashMap::default();
for i in 0..self.points {
if let Some(expiring) = expiring.remove(&i) {
for (ty, local) in expiring {
log::trace!(" -> expiring {} of type {} back to freelist", local, ty);
freelist.entry(ty).or_insert_with(|| vec![]).push(local);
}
}
while range_idx < ranges.len() && ranges[range_idx].1.start == i {
let (value, range) = ranges[range_idx].clone();
range_idx += 1;
log::trace!(
"localify: processing range for {}: {}..{}",
value,
range.start,
range.end
);
if let &ValueDef::BlockParam(b, _, _) = &self.body.values[value] {
if b == self.body.entry {
continue;
}
}
let mut allocs = smallvec![];
let expiring = expiring.entry(range.end).or_insert_with(|| smallvec![]);
for &ty in self.body.values[value].tys(&self.body.type_pool) {
let local = freelist
.get_mut(&ty)
.and_then(|v| v.pop())
.unwrap_or_else(|| {
log::trace!(" -> allocating new local of type {}", ty);
self.results.locals.push(ty)
});
log::trace!(" -> got local {} of type {}", local, ty);
allocs.push(local);
expiring.push((ty, local));
}
self.results.values[value] = allocs;
}
}
}
fn compute(mut self) -> Localifier {
self.find_cross_block_values();
self.compute_liveness();
self.find_ranges();
self.allocate();
self.results
}
}