use super::{TaggedProvider, TypedProvider};
use crate::{
engine::{
bytecode::{Register, RegisterSpan},
TranslationError,
},
Error,
};
use core::{
cmp::{max, min},
num::NonZeroUsize,
};
use multi_stash::{Key, Key as StashKey, MultiStash};
use std::collections::BTreeSet;
#[cfg(doc)]
use crate::engine::translator::InstrEncoder;
#[derive(Debug, Default)]
pub struct RegisterAlloc {
preservations: MultiStash<()>,
removed_preserved: BTreeSet<Key>,
phase: AllocPhase,
len_locals: u16,
next_dynamic: i16,
max_dynamic: i16,
min_preserve: i16,
defrag_offset: i16,
}
#[derive(Debug, Default, Copy, Clone)]
enum AllocPhase {
#[default]
Init,
Alloc,
Defrag,
}
#[derive(Debug, Copy, Clone)]
pub enum RegisterSpace {
Const,
Local,
Dynamic,
Preserve,
}
impl RegisterAlloc {
const MAX_LEN_LOCALS: u16 = i16::MAX as u16 - 1;
const INITIAL_PRESERVATION_INDEX: i16 = i16::MAX - 1;
pub fn reset(&mut self) {
self.preservations.clear();
self.phase = AllocPhase::Init;
self.len_locals = 0;
self.next_dynamic = 0;
self.max_dynamic = 0;
self.min_preserve = Self::INITIAL_PRESERVATION_INDEX;
self.defrag_offset = 0;
}
pub fn pop_provider(&mut self, provider: TaggedProvider) -> TypedProvider {
match provider {
TaggedProvider::Local(reg) => TypedProvider::Register(reg),
TaggedProvider::Dynamic(reg) => {
self.pop_dynamic();
TypedProvider::Register(reg)
}
TaggedProvider::Preserved(reg) => {
self.pop_preserved(reg);
TypedProvider::Register(reg)
}
TaggedProvider::ConstLocal(reg) => TypedProvider::Register(reg),
TaggedProvider::ConstValue(value) => TypedProvider::Const(value),
}
}
pub fn register_space(&self, register: Register) -> RegisterSpace {
if register.is_const() {
return RegisterSpace::Const;
}
if self.is_local(register) {
return RegisterSpace::Local;
}
if self.is_preserved(register) {
return RegisterSpace::Preserve;
}
RegisterSpace::Dynamic
}
pub fn len_locals(&self) -> u16 {
self.len_locals
}
fn min_dynamic(&self) -> i16 {
self.len_locals() as i16
}
pub fn len_registers(&self) -> u16 {
Self::MAX_LEN_LOCALS - self.max_dynamic.abs_diff(self.min_preserve)
}
pub fn register_locals(&mut self, amount: u32) -> Result<(), Error> {
fn bump_locals(len_locals: u16, amount: u32) -> Option<u16> {
let amount = u16::try_from(amount).ok()?;
let new_len = len_locals.checked_add(amount)?;
if new_len >= RegisterAlloc::MAX_LEN_LOCALS {
return None;
}
Some(new_len)
}
assert!(matches!(self.phase, AllocPhase::Init));
self.len_locals = bump_locals(self.len_locals, amount)
.ok_or_else(|| Error::from(TranslationError::AllocatedTooManyRegisters))?;
self.next_dynamic = self.len_locals as i16;
self.max_dynamic = self.len_locals as i16;
Ok(())
}
pub fn finish_register_locals(&mut self) {
assert!(matches!(self.phase, AllocPhase::Init));
self.phase = AllocPhase::Alloc;
}
fn assert_alloc_phase(&self) {
assert!(matches!(self.phase, AllocPhase::Alloc));
}
pub fn push_dynamic(&mut self) -> Result<Register, Error> {
self.assert_alloc_phase();
if self.next_dynamic == self.min_preserve {
return Err(Error::from(TranslationError::AllocatedTooManyRegisters));
}
let reg = Register::from_i16(self.next_dynamic);
self.next_dynamic += 1;
self.max_dynamic = max(self.max_dynamic, self.next_dynamic);
Ok(reg)
}
pub fn push_dynamic_n(&mut self, n: usize) -> Result<RegisterSpan, Error> {
fn next_dynamic_n(this: &mut RegisterAlloc, n: usize) -> Option<RegisterSpan> {
let n = i16::try_from(n).ok()?;
let next_dynamic = this.next_dynamic.checked_add(n)?;
if next_dynamic >= this.min_preserve {
return None;
}
let register = RegisterSpan::new(Register::from_i16(this.next_dynamic));
this.next_dynamic += n;
this.max_dynamic = max(this.max_dynamic, this.next_dynamic);
Some(register)
}
self.assert_alloc_phase();
next_dynamic_n(self, n)
.ok_or_else(|| Error::from(TranslationError::AllocatedTooManyRegisters))
}
fn pop_dynamic(&mut self) {
self.assert_alloc_phase();
assert_ne!(
self.next_dynamic,
self.min_dynamic(),
"dynamic register allocation stack is empty"
);
self.next_dynamic -= 1;
}
pub fn pop_dynamic_n(&mut self, n: usize) {
fn pop_impl(this: &mut RegisterAlloc, n: usize) -> Option<()> {
let n = i16::try_from(n).ok()?;
let new_next_dynamic = this.next_dynamic.checked_sub(n)?;
if new_next_dynamic < this.min_dynamic() {
return None;
}
this.next_dynamic = new_next_dynamic;
Some(())
}
self.assert_alloc_phase();
pop_impl(self, n).expect("dynamic register underflow")
}
pub fn push_preserved(&mut self) -> Result<Register, Error> {
const NZ_TWO: NonZeroUsize = match NonZeroUsize::new(2) {
Some(value) => value,
None => unreachable!(),
};
self.assert_alloc_phase();
self.removed_preserved.clear();
let key = self.preservations.put(NZ_TWO, ());
let reg = Self::key2reg(key);
self.update_min_preserved(reg.prev())?;
Ok(reg)
}
pub fn gc_preservations(&mut self) {
self.assert_alloc_phase();
if self.removed_preserved.is_empty() {
return;
}
for &key in &self.removed_preserved {
let entry = self.preservations.get(key);
debug_assert!(
!matches!(entry, Some((0, _))),
"found preserved register allocation entry with invalid 0 amount"
);
if let Some((1, _)) = entry {
self.preservations.take_all(key);
}
}
}
pub fn bump_preserved(&mut self, register: Register) {
debug_assert!(matches!(
self.register_space(register),
RegisterSpace::Preserve
));
let key = Self::reg2key(register);
let old_amount = self.preservations.bump(key, 1);
debug_assert!(
old_amount.is_some()
);
}
fn pop_preserved(&mut self, register: Register) {
self.assert_alloc_phase();
let key = Self::reg2key(register);
self.removed_preserved.insert(key);
self.preservations
.take_one(key)
.unwrap_or_else(|| panic!("missing preservation slot for {register:?}"));
}
fn update_min_preserved(&mut self, register: Register) -> Result<(), Error> {
self.min_preserve = min(self.min_preserve, register.to_i16());
if self.next_dynamic == self.min_preserve {
return Err(Error::from(TranslationError::AllocatedTooManyRegisters));
}
Ok(())
}
fn reg2key(register: Register) -> StashKey {
let reg_index = Self::INITIAL_PRESERVATION_INDEX - register.to_i16();
let key_index = usize::try_from(reg_index).unwrap_or_else(|error| {
panic!("reg_index ({reg_index}) must be convertible to usize: {error}")
});
StashKey::from(key_index)
}
fn key2reg(key: StashKey) -> Register {
let key_index = usize::from(key);
let reg_index = Self::INITIAL_PRESERVATION_INDEX
- i16::try_from(key_index).unwrap_or_else(|error| {
panic!(
"key_index ({key_index}) must be convertible to positive i16 integer: {error}"
)
});
Register::from_i16(reg_index)
}
pub fn is_local(&self, reg: Register) -> bool {
!reg.is_const() && reg.to_i16() < self.min_dynamic()
}
fn is_preserved(&self, reg: Register) -> bool {
self.min_preserve < reg.to_i16()
}
pub fn finalize_alloc(&mut self) {
assert!(matches!(self.phase, AllocPhase::Alloc));
self.phase = AllocPhase::Defrag;
self.defrag_offset = (self.min_preserve - self.max_dynamic).saturating_add(1);
}
pub fn defrag_register(&self, register: Register) -> Register {
assert!(matches!(self.phase, AllocPhase::Defrag));
if !self.is_preserved(register) {
return register;
}
Register::from_i16(register.to_i16() - self.defrag_offset)
}
pub fn inc_register_usage(&mut self, register: Register) {
if !self.is_preserved(register) {
return;
}
self.bump_preserved(register)
}
pub fn dec_register_usage(&mut self, register: Register) {
if !self.is_preserved(register) {
return;
}
self.pop_preserved(register)
}
}