316 lines
9.8 KiB
Rust
316 lines
9.8 KiB
Rust
//! Register diversions.
|
|
//!
|
|
//! Normally, a value is assigned to a single register or stack location by the register allocator.
|
|
//! Sometimes, it is necessary to move register values to a different register in order to satisfy
|
|
//! instruction constraints.
|
|
//!
|
|
//! These register diversions are local to a block. No values can be diverted when entering a new
|
|
//! block.
|
|
|
|
use crate::fx::FxHashMap;
|
|
use crate::hash_map::{Entry, Iter};
|
|
use crate::ir::{Block, StackSlot, Value, ValueLoc, ValueLocations};
|
|
use crate::ir::{InstructionData, Opcode};
|
|
use crate::isa::{RegInfo, RegUnit};
|
|
use core::fmt;
|
|
use cranelift_entity::{SparseMap, SparseMapValue};
|
|
|
|
/// A diversion of a value from its original location to a new register or stack location.
|
|
///
|
|
/// In IR, a diversion is represented by a `regmove` instruction, possibly a chain of them for the
|
|
/// same value.
|
|
///
|
|
/// When tracking diversions, the `from` field is the original assigned value location, and `to` is
|
|
/// the current one.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct Diversion {
|
|
/// The original value location.
|
|
pub from: ValueLoc,
|
|
/// The current value location.
|
|
pub to: ValueLoc,
|
|
}
|
|
|
|
impl Diversion {
|
|
/// Make a new diversion.
|
|
pub fn new(from: ValueLoc, to: ValueLoc) -> Self {
|
|
debug_assert!(from.is_assigned() && to.is_assigned());
|
|
Self { from, to }
|
|
}
|
|
}
|
|
|
|
/// Keep track of diversions in a block.
|
|
#[derive(Clone)]
|
|
pub struct RegDiversions {
|
|
current: FxHashMap<Value, Diversion>,
|
|
}
|
|
|
|
/// Keep track of diversions at the entry of block.
|
|
#[derive(Clone)]
|
|
struct EntryRegDiversionsValue {
|
|
key: Block,
|
|
divert: RegDiversions,
|
|
}
|
|
|
|
/// Map block to their matching RegDiversions at basic blocks entry.
|
|
pub struct EntryRegDiversions {
|
|
map: SparseMap<Block, EntryRegDiversionsValue>,
|
|
}
|
|
|
|
impl RegDiversions {
|
|
/// Create a new empty diversion tracker.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
current: FxHashMap::default(),
|
|
}
|
|
}
|
|
|
|
/// Clear the content of the diversions, to reset the state of the compiler.
|
|
pub fn clear(&mut self) {
|
|
self.current.clear()
|
|
}
|
|
|
|
/// Are there any diversions?
|
|
pub fn is_empty(&self) -> bool {
|
|
self.current.is_empty()
|
|
}
|
|
|
|
/// Get the current diversion of `value`, if any.
|
|
pub fn diversion(&self, value: Value) -> Option<&Diversion> {
|
|
self.current.get(&value)
|
|
}
|
|
|
|
/// Get all current diversions.
|
|
pub fn iter(&self) -> Iter<'_, Value, Diversion> {
|
|
self.current.iter()
|
|
}
|
|
|
|
/// Get the current location for `value`. Fall back to the assignment map for non-diverted
|
|
/// values
|
|
pub fn get(&self, value: Value, locations: &ValueLocations) -> ValueLoc {
|
|
match self.diversion(value) {
|
|
Some(d) => d.to,
|
|
None => locations[value],
|
|
}
|
|
}
|
|
|
|
/// Get the current register location for `value`, or panic if `value` isn't in a register.
|
|
pub fn reg(&self, value: Value, locations: &ValueLocations) -> RegUnit {
|
|
self.get(value, locations).unwrap_reg()
|
|
}
|
|
|
|
/// Get the current stack location for `value`, or panic if `value` isn't in a stack slot.
|
|
pub fn stack(&self, value: Value, locations: &ValueLocations) -> StackSlot {
|
|
self.get(value, locations).unwrap_stack()
|
|
}
|
|
|
|
/// Record any kind of move.
|
|
///
|
|
/// The `from` location must match an existing `to` location, if any.
|
|
fn divert(&mut self, value: Value, from: ValueLoc, to: ValueLoc) {
|
|
debug_assert!(from.is_assigned() && to.is_assigned());
|
|
match self.current.entry(value) {
|
|
Entry::Occupied(mut e) => {
|
|
// TODO: non-lexical lifetimes should allow removal of the scope and early return.
|
|
{
|
|
let d = e.get_mut();
|
|
debug_assert_eq!(d.to, from, "Bad regmove chain for {}", value);
|
|
if d.from != to {
|
|
d.to = to;
|
|
return;
|
|
}
|
|
}
|
|
e.remove();
|
|
}
|
|
Entry::Vacant(e) => {
|
|
e.insert(Diversion::new(from, to));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Record a register -> register move.
|
|
pub fn regmove(&mut self, value: Value, from: RegUnit, to: RegUnit) {
|
|
self.divert(value, ValueLoc::Reg(from), ValueLoc::Reg(to));
|
|
}
|
|
|
|
/// Record a register -> stack move.
|
|
pub fn regspill(&mut self, value: Value, from: RegUnit, to: StackSlot) {
|
|
self.divert(value, ValueLoc::Reg(from), ValueLoc::Stack(to));
|
|
}
|
|
|
|
/// Record a stack -> register move.
|
|
pub fn regfill(&mut self, value: Value, from: StackSlot, to: RegUnit) {
|
|
self.divert(value, ValueLoc::Stack(from), ValueLoc::Reg(to));
|
|
}
|
|
|
|
/// Apply the effect of `inst`.
|
|
///
|
|
/// If `inst` is a `regmove`, `regfill`, or `regspill` instruction, update the diversions to
|
|
/// match.
|
|
pub fn apply(&mut self, inst: &InstructionData) {
|
|
match *inst {
|
|
InstructionData::RegMove {
|
|
opcode: Opcode::Regmove,
|
|
arg,
|
|
src,
|
|
dst,
|
|
} => self.regmove(arg, src, dst),
|
|
InstructionData::RegSpill {
|
|
opcode: Opcode::Regspill,
|
|
arg,
|
|
src,
|
|
dst,
|
|
} => self.regspill(arg, src, dst),
|
|
InstructionData::RegFill {
|
|
opcode: Opcode::Regfill,
|
|
arg,
|
|
src,
|
|
dst,
|
|
} => self.regfill(arg, src, dst),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
/// Drop any recorded move for `value`.
|
|
///
|
|
/// Returns the `to` location of the removed diversion.
|
|
pub fn remove(&mut self, value: Value) -> Option<ValueLoc> {
|
|
self.current.remove(&value).map(|d| d.to)
|
|
}
|
|
|
|
/// Resets the state of the current diversions to the recorded diversions at the entry of the
|
|
/// given `block`. The recoded diversions is available after coloring on `func.entry_diversions`
|
|
/// field.
|
|
pub fn at_block(&mut self, entry_diversions: &EntryRegDiversions, block: Block) {
|
|
self.clear();
|
|
if let Some(entry_divert) = entry_diversions.map.get(block) {
|
|
let iter = entry_divert.divert.current.iter();
|
|
self.current.extend(iter);
|
|
}
|
|
}
|
|
|
|
/// Copy the current state of the diversions, and save it for the entry of the `block` given as
|
|
/// argument.
|
|
///
|
|
/// Note: This function can only be called once on a `Block` with a given `entry_diversions`
|
|
/// argument, otherwise it would panic.
|
|
pub fn save_for_block(&mut self, entry_diversions: &mut EntryRegDiversions, target: Block) {
|
|
// No need to save anything if there is no diversions to be recorded.
|
|
if self.is_empty() {
|
|
return;
|
|
}
|
|
debug_assert!(!entry_diversions.map.contains_key(target));
|
|
let iter = self.current.iter();
|
|
let mut entry_divert = Self::new();
|
|
entry_divert.current.extend(iter);
|
|
entry_diversions.map.insert(EntryRegDiversionsValue {
|
|
key: target,
|
|
divert: entry_divert,
|
|
});
|
|
}
|
|
|
|
/// Check that the recorded entry for a given `block` matches what is recorded in the
|
|
/// `entry_diversions`.
|
|
pub fn check_block_entry(&self, entry_diversions: &EntryRegDiversions, target: Block) -> bool {
|
|
let entry_divert = match entry_diversions.map.get(target) {
|
|
Some(entry_divert) => entry_divert,
|
|
None => return self.is_empty(),
|
|
};
|
|
|
|
if entry_divert.divert.current.len() != self.current.len() {
|
|
return false;
|
|
}
|
|
|
|
for (val, _) in entry_divert.divert.current.iter() {
|
|
if !self.current.contains_key(val) {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
/// Return an object that can display the diversions.
|
|
pub fn display<'a, R: Into<Option<&'a RegInfo>>>(&'a self, regs: R) -> DisplayDiversions<'a> {
|
|
DisplayDiversions(&self, regs.into())
|
|
}
|
|
}
|
|
|
|
impl EntryRegDiversions {
|
|
/// Create a new empty entry diversion, to associate diversions to each block entry.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
map: SparseMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn clear(&mut self) {
|
|
self.map.clear();
|
|
}
|
|
}
|
|
|
|
impl Clone for EntryRegDiversions {
|
|
/// The Clone trait is required by `ir::Function`.
|
|
fn clone(&self) -> Self {
|
|
let mut tmp = Self::new();
|
|
for v in self.map.values() {
|
|
tmp.map.insert(v.clone());
|
|
}
|
|
tmp
|
|
}
|
|
}
|
|
|
|
/// Implement `SparseMapValue`, as required to make use of a `SparseMap` for mapping the entry
|
|
/// diversions for each block.
|
|
impl SparseMapValue<Block> for EntryRegDiversionsValue {
|
|
fn key(&self) -> Block {
|
|
self.key
|
|
}
|
|
}
|
|
|
|
/// Object that displays register diversions.
|
|
pub struct DisplayDiversions<'a>(&'a RegDiversions, Option<&'a RegInfo>);
|
|
|
|
impl<'a> fmt::Display for DisplayDiversions<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{{")?;
|
|
for (value, div) in self.0.current.iter() {
|
|
write!(
|
|
f,
|
|
" {}: {} -> {}",
|
|
value,
|
|
div.from.display(self.1),
|
|
div.to.display(self.1)
|
|
)?
|
|
}
|
|
write!(f, " }}")
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::entity::EntityRef;
|
|
use crate::ir::Value;
|
|
|
|
#[test]
|
|
fn inserts() {
|
|
let mut divs = RegDiversions::new();
|
|
let v1 = Value::new(1);
|
|
let v2 = Value::new(2);
|
|
|
|
divs.regmove(v1, 10, 12);
|
|
assert_eq!(
|
|
divs.diversion(v1),
|
|
Some(&Diversion {
|
|
from: ValueLoc::Reg(10),
|
|
to: ValueLoc::Reg(12),
|
|
})
|
|
);
|
|
assert_eq!(divs.diversion(v2), None);
|
|
|
|
divs.regmove(v1, 12, 11);
|
|
assert_eq!(divs.diversion(v1).unwrap().to, ValueLoc::Reg(11));
|
|
divs.regmove(v1, 11, 10);
|
|
assert_eq!(divs.diversion(v1), None);
|
|
}
|
|
}
|