use alloc::{sync::Arc, vec::Vec};
use miden_core::{mast::MastNodeId, program::Program};
use miden_mast_package::debug_info::{DebugSourceNodeId, PackageDebugInfo};
const CONTINUATION_STACK_SIZE_HINT: usize = 64;
#[derive(Debug, Clone)]
pub enum Continuation<F> {
StartNode(MastNodeId),
FinishJoin(MastNodeId),
FinishSplit(MastNodeId),
FinishLoop(MastNodeId),
FinishCall(MastNodeId),
FinishDyn(MastNodeId),
ResumeBasicBlock {
node_id: MastNodeId,
batch_index: usize,
op_idx_in_batch: usize,
},
Respan { node_id: MastNodeId, batch_index: usize },
FinishBasicBlock(MastNodeId),
EnterForest {
forest: F,
package_debug_info: Option<Arc<PackageDebugInfo>>,
},
}
impl<F> Continuation<F> {
pub fn increments_clk(&self) -> bool {
use Continuation::*;
match self {
StartNode(_)
| FinishJoin(_)
| FinishSplit(_)
| FinishLoop(_)
| FinishCall(_)
| FinishDyn(_)
| ResumeBasicBlock {
node_id: _,
batch_index: _,
op_idx_in_batch: _,
}
| Respan { node_id: _, batch_index: _ }
| FinishBasicBlock(_) => true,
EnterForest { .. } => false,
}
}
#[cfg(any(test, feature = "testing"))]
pub(crate) fn exec_node(&self) -> Option<MastNodeId> {
match self {
Self::StartNode(node_id)
| Self::FinishJoin(node_id)
| Self::FinishSplit(node_id)
| Self::FinishLoop(node_id)
| Self::FinishCall(node_id)
| Self::FinishDyn(node_id)
| Self::ResumeBasicBlock { node_id, .. }
| Self::Respan { node_id, .. }
| Self::FinishBasicBlock(node_id) => Some(*node_id),
Self::EnterForest { .. } => None,
}
}
}
#[derive(Debug, Clone)]
pub struct ContinuationStack<F> {
stack: Vec<Continuation<F>>,
source_node_ids: Option<Vec<Option<DebugSourceNodeId>>>,
}
impl<F> Default for ContinuationStack<F> {
fn default() -> Self {
Self { stack: Vec::new(), source_node_ids: None }
}
}
impl<F> ContinuationStack<F> {
pub fn new(program: &Program) -> Self {
let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
stack.push(Continuation::StartNode(program.entrypoint()));
Self { stack, source_node_ids: None }
}
pub(crate) fn new_with_source_node_id(
program: &Program,
source_node_id: DebugSourceNodeId,
) -> Self {
Self::new_with_optional_source_node_id(program, Some(source_node_id))
}
pub(crate) fn new_with_optional_source_node_id(
program: &Program,
source_node_id: Option<DebugSourceNodeId>,
) -> Self {
let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
stack.push(Continuation::StartNode(program.entrypoint()));
let mut source_node_ids = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
source_node_ids.push(source_node_id);
Self {
stack,
source_node_ids: Some(source_node_ids),
}
}
pub fn push_continuation(&mut self, continuation: Continuation<F>) {
self.stack.push(continuation);
self.push_source_node_id(None);
}
pub(crate) fn push_with_source_node_id(
&mut self,
continuation: Continuation<F>,
source_node_id: Option<DebugSourceNodeId>,
) {
self.stack.push(continuation);
self.push_source_node_id(source_node_id);
}
pub fn push_enter_forest(&mut self, forest: F) {
self.push_enter_forest_with_package_debug_info(forest, None);
}
pub(crate) fn push_enter_forest_with_package_debug_info(
&mut self,
forest: F,
package_debug_info: Option<Arc<PackageDebugInfo>>,
) {
self.stack.push(Continuation::EnterForest { forest, package_debug_info });
self.push_source_node_id(None);
}
pub fn push_finish_join(&mut self, node_id: MastNodeId) {
self.stack.push(Continuation::FinishJoin(node_id));
self.push_source_node_id(None);
}
pub fn push_finish_split(&mut self, node_id: MastNodeId) {
self.stack.push(Continuation::FinishSplit(node_id));
self.push_source_node_id(None);
}
pub fn push_finish_loop(&mut self, node_id: MastNodeId) {
self.stack.push(Continuation::FinishLoop(node_id));
self.push_source_node_id(None);
}
pub fn push_finish_call(&mut self, node_id: MastNodeId) {
self.stack.push(Continuation::FinishCall(node_id));
self.push_source_node_id(None);
}
pub fn push_finish_dyn(&mut self, node_id: MastNodeId) {
self.stack.push(Continuation::FinishDyn(node_id));
self.push_source_node_id(None);
}
pub fn push_start_node(&mut self, node_id: MastNodeId) {
self.stack.push(Continuation::StartNode(node_id));
self.push_source_node_id(None);
}
pub fn pop_continuation(&mut self) -> Option<Continuation<F>> {
let continuation = self.stack.pop()?;
if let Some(source_node_ids) = &mut self.source_node_ids {
source_node_ids.pop();
}
Some(continuation)
}
pub(crate) fn pop_continuation_with_source_node_id(
&mut self,
) -> Option<(Continuation<F>, Option<DebugSourceNodeId>)> {
let continuation = self.stack.pop()?;
let source_node_id = self.source_node_ids.as_mut().and_then(Vec::pop).flatten();
Some((continuation, source_node_id))
}
pub fn into_inner(self) -> Vec<Continuation<F>> {
self.stack
}
fn push_source_node_id(&mut self, source_node_id: Option<DebugSourceNodeId>) {
if let Some(source_node_ids) = &mut self.source_node_ids {
source_node_ids.push(source_node_id);
}
}
#[cfg(any(test, feature = "testing"))]
pub(crate) fn start_tracking_source_nodes(
&mut self,
next_source_node_id: Option<DebugSourceNodeId>,
) {
let mut source_node_ids = Vec::with_capacity(self.stack.len());
source_node_ids.resize(self.stack.len(), None);
if let Some(source_node_id) = source_node_ids.last_mut() {
*source_node_id = next_source_node_id;
}
self.source_node_ids = Some(source_node_ids);
}
pub fn len(&self) -> usize {
self.stack.len()
}
pub fn peek_continuation(&self) -> Option<&Continuation<F>> {
self.stack.last()
}
pub(crate) fn peek_continuation_with_source_node_id(
&self,
) -> Option<(&Continuation<F>, Option<DebugSourceNodeId>)> {
let continuation = self.stack.last()?;
let source_node_id = self
.source_node_ids
.as_ref()
.and_then(|source_node_ids| source_node_ids.last().copied().flatten());
Some((continuation, source_node_id))
}
pub(crate) fn tracks_source_nodes(&self) -> bool {
self.source_node_ids.is_some()
}
pub fn iter_continuations_for_next_clock(&self) -> impl Iterator<Item = &Continuation<F>> {
let mut found_incrementing_cont = false;
self.stack.iter().rev().take_while(move |continuation| {
if found_incrementing_cont {
false
} else if continuation.increments_clk() {
found_incrementing_cont = true;
true
} else {
true
}
})
}
}
#[cfg(test)]
mod tests {
use alloc::sync::Arc;
use miden_core::mast::MastForest;
use super::*;
#[test]
fn get_next_clock_cycle_increment_empty_stack() {
let stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
assert!(stack.iter_continuations_for_next_clock().next().is_none());
}
#[test]
fn get_next_clock_cycle_increment_ends_with_incrementing() {
let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
assert_eq!(result.len(), 1);
assert!(matches!(result[0], Continuation::StartNode(_)));
}
#[test]
fn get_next_clock_cycle_increment_enter_forest_after_incrementing() {
let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
stack.push_continuation(Continuation::EnterForest {
forest: Arc::new(MastForest::new()),
package_debug_info: None,
});
let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
assert_eq!(result.len(), 2);
assert!(matches!(result[0], Continuation::EnterForest { .. }));
assert!(matches!(result[1], Continuation::StartNode(_)));
}
#[test]
fn get_next_clock_cycle_increment_multiple_enter_forest_after_incrementing() {
let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
stack.push_continuation(Continuation::EnterForest {
forest: Arc::new(MastForest::new()),
package_debug_info: None,
});
stack.push_continuation(Continuation::EnterForest {
forest: Arc::new(MastForest::new()),
package_debug_info: None,
});
let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
assert_eq!(result.len(), 3);
assert!(matches!(result[0], Continuation::EnterForest { .. }));
assert!(matches!(result[1], Continuation::EnterForest { .. }));
assert!(matches!(result[2], Continuation::StartNode(_)));
}
}