use miden_air::trace::RowIndex;
use miden_utils_indexing::IndexVec;
use super::{Felt, ZERO};
use crate::operation::OperationError;
#[derive(Debug, Clone, Default)]
struct OverflowStackEntry {
pub value: Felt,
pub clk: RowIndex,
}
impl OverflowStackEntry {
pub fn new(value: Felt, clk: RowIndex) -> Self {
Self { value, clk }
}
pub fn value(&self) -> Felt {
self.value
}
}
#[derive(Debug, Default, Clone)]
struct OverflowStack {
overflow: IndexVec<RowIndex, OverflowStackEntry>,
}
impl OverflowStack {
pub fn new() -> Self {
Self { overflow: IndexVec::new() }
}
pub fn last(&self) -> Option<&OverflowStackEntry> {
self.overflow.as_slice().last()
}
pub fn num_elements(&self) -> usize {
self.overflow.len()
}
pub fn is_empty(&self) -> bool {
self.overflow.is_empty()
}
pub fn push(&mut self, entry: OverflowStackEntry) {
let _ = self.overflow.push(entry); }
pub fn pop(&mut self) -> Option<OverflowStackEntry> {
if self.overflow.is_empty() {
None
} else {
Some(self.overflow.swap_remove(self.overflow.len() - 1))
}
}
}
#[derive(Debug, Clone)]
pub struct OverflowTable {
overflow: IndexVec<RowIndex, OverflowStack>,
}
impl OverflowTable {
pub fn new() -> Self {
let mut overflow = IndexVec::new();
let _ = overflow.push(OverflowStack::new());
Self { overflow }
}
pub fn last_update_clk_in_current_ctx(&self) -> Felt {
self.get_current_overflow_stack()
.expect("overflow table should always have at least one stack")
.last()
.map_or(ZERO, |entry| Felt::from(entry.clk))
}
pub fn clk_after_pop_in_current_ctx(&self) -> Felt {
let stack = self
.get_current_overflow_stack()
.expect("overflow table should always have at least one stack");
let entries = stack.overflow.as_slice();
if entries.len() < 2 {
ZERO
} else {
Felt::from(entries[entries.len() - 2].clk)
}
}
pub fn num_elements_in_current_ctx(&self) -> usize {
self.get_current_overflow_stack()
.expect("overflow table should always have at least one stack")
.num_elements()
}
pub fn push(&mut self, value: Felt, clk: RowIndex) {
self.get_current_overflow_stack_mut()
.expect("overflow table should always have at least one stack")
.push(OverflowStackEntry::new(value, clk));
}
pub fn pop(&mut self) -> Option<Felt> {
self.get_current_overflow_stack_mut()
.expect("overflow table should always have at least one stack")
.pop()
.as_ref()
.map(OverflowStackEntry::value)
}
pub fn start_context(&mut self) {
let _ = self.overflow.push(OverflowStack::new()); }
pub fn restore_context(&mut self) -> Result<(), OperationError> {
let len = self.overflow.len();
if len <= 1 {
return Err(OperationError::Internal(
"cannot restore context: must have at least one child context above the root stack",
));
}
let is_empty = self.overflow.as_slice().last().expect("len > 0").is_empty();
if !is_empty {
return Err(OperationError::Internal(
"cannot restore context: overflow stack for the current context is not empty",
));
}
self.overflow.swap_remove(len - 1);
Ok(())
}
fn get_current_overflow_stack(&self) -> Result<&OverflowStack, OperationError> {
self.overflow.as_slice().last().ok_or(OperationError::Internal(
"the current context should always have an overflow stack initialized",
))
}
fn get_current_overflow_stack_mut(&mut self) -> Result<&mut OverflowStack, OperationError> {
let len = self.overflow.len();
if len == 0 {
return Err(OperationError::Internal(
"the current context should always have an overflow stack initialized",
));
}
Ok(&mut self.overflow[RowIndex::from(len - 1)])
}
}
impl Default for OverflowTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use miden_air::trace::RowIndex;
use super::*;
#[test]
fn restore_context_rejects_root_only_table() {
let mut table = OverflowTable::new();
let result = table.restore_context();
assert!(result.is_err(), "restore_context should reject root-only table");
assert_eq!(table.num_elements_in_current_ctx(), 0);
}
#[test]
fn restore_context_rejects_non_empty_child_stack() {
let mut table = OverflowTable::new();
table.start_context();
table.push(Felt::new(42).unwrap(), RowIndex::from(1));
let result = table.restore_context();
assert!(result.is_err(), "restore_context should reject non-empty child stack");
assert_eq!(table.num_elements_in_current_ctx(), 1);
}
#[test]
fn restore_context_succeeds_with_empty_child_stack() {
let mut table = OverflowTable::new();
table.start_context();
let result = table.restore_context();
assert!(result.is_ok(), "restore_context should succeed with empty child stack");
assert_eq!(table.num_elements_in_current_ctx(), 0);
let result2 = table.restore_context();
assert!(result2.is_err(), "restore_context on root stack should fail");
}
}