use crate::mir::ValueId;
use solar_data_structures::map::{FxHashMap, FxHashSet};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SpillSlot {
pub offset: u32,
}
impl SpillSlot {
#[must_use]
pub const fn byte_offset(&self) -> u32 {
0x1000 + self.offset * 32
}
}
#[derive(Clone, Debug)]
pub struct SpillManager {
slots: FxHashMap<ValueId, SpillSlot>,
reloadable: FxHashSet<ValueId>,
stored: FxHashSet<ValueId>,
next_offset: u32,
max_offset: u32,
}
impl SpillManager {
#[must_use]
pub fn new() -> Self {
Self {
slots: FxHashMap::default(),
reloadable: FxHashSet::default(),
stored: FxHashSet::default(),
next_offset: 0,
max_offset: 0,
}
}
pub fn allocate(&mut self, value: ValueId) -> SpillSlot {
if let Some(&slot) = self.slots.get(&value) {
return slot;
}
let slot = SpillSlot { offset: self.next_offset };
self.slots.insert(value, slot);
self.next_offset += 1;
self.max_offset = self.max_offset.max(self.next_offset);
slot
}
#[must_use]
pub fn get(&self, value: ValueId) -> Option<SpillSlot> {
self.slots.get(&value).copied()
}
#[must_use]
pub fn is_spilled(&self, value: ValueId) -> bool {
self.slots.contains_key(&value)
}
pub fn mark_reloadable(&mut self, value: ValueId) {
debug_assert!(self.slots.contains_key(&value));
self.reloadable.insert(value);
}
pub fn mark_stored(&mut self, value: ValueId) {
debug_assert!(self.slots.contains_key(&value));
self.reloadable.insert(value);
self.stored.insert(value);
}
#[must_use]
pub fn is_reloadable(&self, value: ValueId) -> bool {
self.reloadable.contains(&value)
}
#[must_use]
pub fn is_stored(&self, value: ValueId) -> bool {
self.stored.contains(&value)
}
pub fn free(&mut self, value: ValueId) {
self.slots.remove(&value);
self.reloadable.remove(&value);
self.stored.remove(&value);
}
#[must_use]
pub fn spill_area_size(&self) -> u32 {
self.max_offset * 32
}
pub fn clear(&mut self) {
self.slots.clear();
self.reloadable.clear();
self.stored.clear();
self.next_offset = 0;
self.max_offset = 0;
}
#[must_use]
pub fn count(&self) -> usize {
self.slots.len()
}
}
impl Default for SpillManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocate() {
let mut manager = SpillManager::new();
let v0 = ValueId::from_usize(0);
let v1 = ValueId::from_usize(1);
let v2 = ValueId::from_usize(2);
let slot0 = manager.allocate(v0);
let slot1 = manager.allocate(v1);
let slot2 = manager.allocate(v2);
assert_eq!(slot0.offset, 0);
assert_eq!(slot1.offset, 1);
assert_eq!(slot2.offset, 2);
assert_eq!(slot0.byte_offset(), 0x1000);
assert_eq!(slot1.byte_offset(), 0x1000 + 32);
assert_eq!(slot2.byte_offset(), 0x1000 + 64);
}
#[test]
fn test_allocate_idempotent() {
let mut manager = SpillManager::new();
let v0 = ValueId::from_usize(0);
let slot1 = manager.allocate(v0);
let slot2 = manager.allocate(v0);
assert_eq!(slot1, slot2);
assert_eq!(manager.count(), 1);
}
#[test]
fn test_free() {
let mut manager = SpillManager::new();
let v0 = ValueId::from_usize(0);
manager.allocate(v0);
assert!(manager.is_spilled(v0));
manager.free(v0);
assert!(!manager.is_spilled(v0));
}
#[test]
fn test_reloadable_and_stored_are_distinct() {
let mut manager = SpillManager::new();
let v0 = ValueId::from_usize(0);
let v1 = ValueId::from_usize(1);
manager.allocate(v0);
assert!(!manager.is_reloadable(v0));
assert!(!manager.is_stored(v0));
manager.mark_reloadable(v0);
assert!(manager.is_reloadable(v0));
assert!(!manager.is_stored(v0));
manager.allocate(v1);
manager.mark_stored(v1);
assert!(manager.is_reloadable(v1));
assert!(manager.is_stored(v1));
}
}