use crate::mir::ValueId;
use smallvec::SmallVec;
#[allow(dead_code)]
pub(crate) const MAX_STACK_ACCESS: usize = 16;
#[allow(dead_code)]
pub(crate) const MAX_STACK_DEPTH: usize = 1024;
#[derive(Clone, Debug)]
pub struct StackModel {
stack: SmallVec<[Option<ValueId>; 16]>,
}
impl StackModel {
#[must_use]
pub fn new() -> Self {
Self { stack: SmallVec::new() }
}
#[must_use]
pub fn depth(&self) -> usize {
self.stack.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn push(&mut self, value: ValueId) {
self.stack.insert(0, Some(value));
}
pub fn push_unknown(&mut self) {
self.stack.insert(0, None);
}
pub fn pop(&mut self) -> Option<ValueId> {
debug_assert!(!self.stack.is_empty(), "Stack underflow");
if self.stack.is_empty() { None } else { self.stack.remove(0) }
}
#[must_use]
pub fn peek(&self, depth: usize) -> Option<ValueId> {
self.stack.get(depth).copied().flatten()
}
#[must_use]
pub fn top(&self) -> Option<ValueId> {
self.peek(0)
}
#[must_use]
pub fn find(&self, value: ValueId) -> Option<usize> {
self.stack.iter().position(|&v| v == Some(value))
}
#[must_use]
pub fn contains(&self, value: ValueId) -> bool {
self.find(value).is_some()
}
#[must_use]
pub fn count(&self, value: ValueId) -> usize {
self.stack.iter().filter(|&&v| v == Some(value)).count()
}
#[must_use]
pub fn is_on_top(&self, value: ValueId) -> bool {
self.peek(0) == Some(value)
}
#[must_use]
pub fn is_accessible(&self, value: ValueId) -> bool {
self.find(value).is_some_and(|d| d < MAX_STACK_ACCESS)
}
pub fn dup(&mut self, n: u8) {
debug_assert!((1..=16).contains(&n), "DUP depth out of range: DUP{n} (valid: 1-16)");
let depth = (n - 1) as usize;
debug_assert!(
depth < self.stack.len(),
"DUP{} attempted but stack only has {} elements",
n,
self.stack.len()
);
if let Some(&value) = self.stack.get(depth) {
self.stack.insert(0, value);
}
}
pub fn swap(&mut self, n: u8) {
debug_assert!((1..=16).contains(&n), "SWAP depth out of range: SWAP{n} (valid: 1-16)");
let depth = n as usize;
debug_assert!(
depth < self.stack.len(),
"SWAP{} attempted but stack only has {} elements",
n,
self.stack.len()
);
if depth < self.stack.len() {
self.stack.swap(0, depth);
}
}
pub fn remove(&mut self, value: ValueId) -> bool {
if let Some(pos) = self.find(value) {
self.stack.remove(pos);
true
} else {
false
}
}
pub fn clear(&mut self) {
self.stack.clear();
}
pub fn iter(&self) -> impl Iterator<Item = Option<ValueId>> + '_ {
self.stack.iter().copied()
}
#[must_use]
pub fn as_slice(&self) -> &[Option<ValueId>] {
&self.stack
}
}
impl Default for StackModel {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StackOp {
Dup(u8),
Swap(u8),
Pop,
}
impl StackOp {
#[must_use]
pub const fn opcode(self) -> u8 {
match self {
Self::Dup(n) => 0x80 + n - 1, Self::Swap(n) => 0x90 + n - 1, Self::Pop => 0x50,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_push_pop() {
let mut model = StackModel::new();
let v0 = ValueId::from_usize(0);
let v1 = ValueId::from_usize(1);
model.push(v0);
model.push(v1);
assert_eq!(model.depth(), 2);
assert_eq!(model.top(), Some(v1));
assert_eq!(model.pop(), Some(v1));
assert_eq!(model.pop(), Some(v0));
assert!(model.is_empty());
}
#[test]
fn test_find() {
let mut model = StackModel::new();
let v0 = ValueId::from_usize(0);
let v1 = ValueId::from_usize(1);
let v2 = ValueId::from_usize(2);
model.push(v0);
model.push(v1);
model.push(v2);
assert_eq!(model.find(v2), Some(0)); assert_eq!(model.find(v1), Some(1));
assert_eq!(model.find(v0), Some(2));
assert_eq!(model.find(ValueId::from_usize(99)), None);
}
#[test]
fn test_dup() {
let mut model = StackModel::new();
let v0 = ValueId::from_usize(0);
let v1 = ValueId::from_usize(1);
model.push(v0);
model.push(v1);
model.dup(1);
assert_eq!(model.depth(), 3);
assert_eq!(model.peek(0), Some(v1));
assert_eq!(model.peek(1), Some(v1));
assert_eq!(model.peek(2), Some(v0));
}
#[test]
fn test_swap() {
let mut model = StackModel::new();
let v0 = ValueId::from_usize(0);
let v1 = ValueId::from_usize(1);
let v2 = ValueId::from_usize(2);
model.push(v0);
model.push(v1);
model.push(v2);
model.swap(1);
assert_eq!(model.peek(0), Some(v1));
assert_eq!(model.peek(1), Some(v2));
assert_eq!(model.peek(2), Some(v0));
}
}