use crate::{engine::bytecode::Register, Error};
use core::mem;
use std::{
collections::{btree_map, BTreeMap},
vec::Vec,
};
#[cfg(doc)]
use super::ProviderStack;
pub type StackIndex = usize;
type EntryIndex = usize;
#[derive(Debug, Default)]
pub struct LocalRefs {
locals_last: BTreeMap<Register, EntryIndex>,
entries: LocalRefsEntries,
}
#[derive(Debug, Default)]
pub struct LocalRefsEntries {
next_free: Option<EntryIndex>,
entries: Vec<LocalRefEntry>,
}
impl LocalRefsEntries {
pub fn reset(&mut self) {
self.next_free = None;
self.entries.clear();
}
#[inline]
pub fn next_free(&self) -> Option<EntryIndex> {
self.next_free
}
#[inline]
pub fn next_index(&self) -> EntryIndex {
self.entries.len()
}
#[inline]
pub fn push_occupied(&mut self, slot: StackIndex, prev: Option<EntryIndex>) -> EntryIndex {
let index = self.next_index();
self.entries.push(LocalRefEntry::Occupied { slot, prev });
index
}
#[inline]
pub fn reuse_vacant(&mut self, index: EntryIndex, slot: StackIndex, prev: Option<EntryIndex>) {
let old_entry = mem::replace(
&mut self.entries[index],
LocalRefEntry::Occupied { slot, prev },
);
self.next_free = match old_entry {
LocalRefEntry::Vacant { next_free } => next_free,
occupied @ LocalRefEntry::Occupied { .. } => {
panic!("tried to reuse occupied entry at index {index}: {occupied:?}")
}
};
}
#[inline]
fn remove_entry(&mut self, index: EntryIndex) -> (Option<EntryIndex>, StackIndex) {
let next_free = self.next_free();
let old_entry = mem::replace(
&mut self.entries[index],
LocalRefEntry::Vacant { next_free },
);
let LocalRefEntry::Occupied { prev, slot } = old_entry else {
panic!("expected occupied entry but found vacant: {old_entry:?}");
};
self.next_free = Some(index);
(prev, slot)
}
}
#[derive(Debug, Copy, Clone)]
enum LocalRefEntry {
Vacant {
next_free: Option<EntryIndex>,
},
Occupied {
slot: StackIndex,
prev: Option<EntryIndex>,
},
}
impl LocalRefs {
pub fn reset(&mut self) {
self.locals_last.clear();
self.entries.reset();
}
pub fn register_locals(&mut self, _amount: u32) {
}
fn update_last(&mut self, index: EntryIndex, local: Register) -> Option<EntryIndex> {
match self.locals_last.entry(local) {
btree_map::Entry::Vacant(entry) => {
entry.insert(index);
None
}
btree_map::Entry::Occupied(mut entry) => {
let prev = *entry.get();
entry.insert(index);
Some(prev)
}
}
}
pub fn push_at(&mut self, local: Register, slot: StackIndex) {
match self.entries.next_free() {
Some(index) => {
let prev = self.update_last(index, local);
self.entries.reuse_vacant(index, slot, prev);
}
None => {
let index = self.entries.next_index();
let prev = self.update_last(index, local);
let pushed = self.entries.push_occupied(slot, prev);
debug_assert_eq!(pushed, index);
}
};
}
#[inline]
fn is_empty(&self) -> bool {
self.locals_last.is_empty()
}
#[inline]
fn reset_if_empty(&mut self) {
if self.is_empty() {
self.entries.reset();
}
}
pub fn pop_at(&mut self, local: Register) -> StackIndex {
let btree_map::Entry::Occupied(mut last) = self.locals_last.entry(local) else {
panic!("missing stack index for local on the provider stack: {local:?}")
};
let index = *last.get();
let (prev, slot) = self.entries.remove_entry(index);
match prev {
Some(prev) => last.insert(prev),
None => last.remove(),
};
self.reset_if_empty();
slot
}
pub fn drain_at(
&mut self,
local: Register,
f: impl FnMut(StackIndex) -> Result<(), Error>,
) -> Result<(), Error> {
let Some(last) = self.locals_last.remove(&local) else {
return Ok(());
};
self.drain_list_at(last, f)?;
self.reset_if_empty();
Ok(())
}
pub fn drain_all(
&mut self,
mut f: impl FnMut(Register, StackIndex) -> Result<(), Error>,
) -> Result<(), Error> {
let local_last = mem::take(&mut self.locals_last);
for (local, last) in &local_last {
let local = *local;
self.drain_list_at(*last, |index| f(local, index))?;
}
self.locals_last = local_last;
self.locals_last.clear();
self.entries.reset();
Ok(())
}
#[inline]
fn drain_list_at(
&mut self,
index: EntryIndex,
mut f: impl FnMut(StackIndex) -> Result<(), Error>,
) -> Result<(), Error> {
let mut last = Some(index);
while let Some(index) = last {
let (prev, slot) = self.entries.remove_entry(index);
last = prev;
f(slot)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn reg(index: i16) -> Register {
Register::from_i16(index)
}
#[test]
fn push_pop_works() {
let mut locals = LocalRefs::default();
locals.push_at(reg(0), 2);
locals.push_at(reg(0), 4);
locals.push_at(reg(1), 6);
locals.push_at(reg(2), 8);
locals.push_at(reg(5), 10);
locals.push_at(reg(1), 12);
locals.push_at(reg(0), 14);
assert_eq!(locals.pop_at(reg(0)), 14);
assert_eq!(locals.pop_at(reg(0)), 4);
assert_eq!(locals.pop_at(reg(0)), 2);
assert_eq!(locals.pop_at(reg(1)), 12);
assert_eq!(locals.pop_at(reg(1)), 6);
assert_eq!(locals.pop_at(reg(2)), 8);
assert_eq!(locals.pop_at(reg(5)), 10);
}
}