use alloc::vec::Vec;
use core::{fmt, mem};
use miden_crypto::{Felt, ZERO, hash::rpo::RpoDigest};
use miden_formatting::prettier::PrettyPrint;
use crate::{
DecoratorIterator, DecoratorList, Operation,
chiplets::hasher,
mast::{DecoratorId, MastForest, MastForestError},
};
mod op_batch;
pub use op_batch::OpBatch;
use op_batch::OpBatchAccumulator;
use super::MastNodeExt;
#[cfg(test)]
mod tests;
pub const GROUP_SIZE: usize = 9;
pub const BATCH_SIZE: usize = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BasicBlockNode {
op_batches: Vec<OpBatch>,
digest: RpoDigest,
decorators: DecoratorList,
}
impl BasicBlockNode {
pub const DOMAIN: Felt = ZERO;
}
impl BasicBlockNode {
pub fn new(
operations: Vec<Operation>,
decorators: Option<DecoratorList>,
) -> Result<Self, MastForestError> {
if operations.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
let decorators = decorators.unwrap_or_default();
#[cfg(debug_assertions)]
validate_decorators(&operations, &decorators);
let (op_batches, digest) = batch_and_hash_ops(operations);
Ok(Self { op_batches, digest, decorators })
}
pub fn new_unsafe(
operations: Vec<Operation>,
decorators: DecoratorList,
digest: RpoDigest,
) -> Self {
assert!(!operations.is_empty());
let op_batches = batch_ops(operations);
Self { op_batches, digest, decorators }
}
#[cfg(test)]
pub fn new_with_raw_decorators(
operations: Vec<Operation>,
decorators: Vec<(usize, crate::Decorator)>,
mast_forest: &mut crate::mast::MastForest,
) -> Result<Self, MastForestError> {
let mut decorator_list = Vec::new();
for (idx, decorator) in decorators {
decorator_list.push((idx, mast_forest.add_decorator(decorator)?));
}
Self::new(operations, Some(decorator_list))
}
}
impl BasicBlockNode {
pub fn digest(&self) -> RpoDigest {
self.digest
}
pub fn op_batches(&self) -> &[OpBatch] {
&self.op_batches
}
pub fn num_op_batches(&self) -> usize {
self.op_batches.len()
}
pub fn num_op_groups(&self) -> usize {
let last_batch_num_groups = self.op_batches.last().expect("no last group").num_groups();
(self.op_batches.len() - 1) * BATCH_SIZE + last_batch_num_groups.next_power_of_two()
}
pub fn num_operations(&self) -> u32 {
let num_ops: usize = self.op_batches.iter().map(|batch| batch.ops().len()).sum();
num_ops.try_into().expect("basic block contains more than 2^32 operations")
}
pub fn decorators(&self) -> &DecoratorList {
&self.decorators
}
pub fn decorator_iter(&self) -> DecoratorIterator {
DecoratorIterator::new(&self.decorators)
}
pub fn operations(&self) -> impl Iterator<Item = &Operation> {
self.op_batches.iter().flat_map(|batch| batch.ops())
}
pub fn num_operations_and_decorators(&self) -> u32 {
let num_ops: usize = self.num_operations() as usize;
let num_decorators = self.decorators.len();
(num_ops + num_decorators)
.try_into()
.expect("basic block contains more than 2^32 operations and decorators")
}
pub fn iter(&self) -> impl Iterator<Item = OperationOrDecorator> {
OperationOrDecoratorIterator::new(self)
}
}
impl BasicBlockNode {
pub fn prepend_decorators(&mut self, decorator_ids: &[DecoratorId]) {
let mut new_decorators: DecoratorList =
decorator_ids.iter().map(|decorator_id| (0, *decorator_id)).collect();
new_decorators.extend(mem::take(&mut self.decorators));
self.decorators = new_decorators;
}
pub fn append_decorators(&mut self, decorator_ids: &[DecoratorId]) {
let after_last_op_idx = self.num_operations() as usize;
self.decorators
.extend(decorator_ids.iter().map(|&decorator_id| (after_last_op_idx, decorator_id)));
}
pub fn set_decorators(&mut self, decorator_list: DecoratorList) {
self.decorators = decorator_list;
}
}
impl MastNodeExt for BasicBlockNode {
fn decorators(&self) -> impl Iterator<Item = (usize, DecoratorId)> {
self.decorators.iter().copied()
}
}
impl BasicBlockNode {
pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
BasicBlockNodePrettyPrint { block_node: self, mast_forest }
}
pub(super) fn to_pretty_print<'a>(
&'a self,
mast_forest: &'a MastForest,
) -> impl PrettyPrint + 'a {
BasicBlockNodePrettyPrint { block_node: self, mast_forest }
}
}
struct BasicBlockNodePrettyPrint<'a> {
block_node: &'a BasicBlockNode,
mast_forest: &'a MastForest,
}
impl PrettyPrint for BasicBlockNodePrettyPrint<'_> {
#[rustfmt::skip]
fn render(&self) -> crate::prettier::Document {
use crate::prettier::*;
let single_line = const_text("basic_block")
+ const_text(" ")
+ self.
block_node
.iter()
.map(|op_or_dec| match op_or_dec {
OperationOrDecorator::Operation(op) => op.render(),
OperationOrDecorator::Decorator(&decorator_id) => self.mast_forest[decorator_id].render(),
})
.reduce(|acc, doc| acc + const_text(" ") + doc)
.unwrap_or_default()
+ const_text(" ")
+ const_text("end");
let multi_line = indent(
4,
const_text("basic_block")
+ nl()
+ self
.block_node
.iter()
.map(|op_or_dec| match op_or_dec {
OperationOrDecorator::Operation(op) => op.render(),
OperationOrDecorator::Decorator(&decorator_id) => self.mast_forest[decorator_id].render(),
})
.reduce(|acc, doc| acc + nl() + doc)
.unwrap_or_default(),
) + nl()
+ const_text("end");
single_line | multi_line
}
}
impl fmt::Display for BasicBlockNodePrettyPrint<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use crate::prettier::PrettyPrint;
self.pretty_print(f)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OperationOrDecorator<'a> {
Operation(&'a Operation),
Decorator(&'a DecoratorId),
}
struct OperationOrDecoratorIterator<'a> {
node: &'a BasicBlockNode,
batch_index: usize,
op_index_in_batch: usize,
op_index: usize,
decorator_list_next_index: usize,
}
impl<'a> OperationOrDecoratorIterator<'a> {
fn new(node: &'a BasicBlockNode) -> Self {
Self {
node,
batch_index: 0,
op_index_in_batch: 0,
op_index: 0,
decorator_list_next_index: 0,
}
}
}
impl<'a> Iterator for OperationOrDecoratorIterator<'a> {
type Item = OperationOrDecorator<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some((op_index, decorator)) =
self.node.decorators.get(self.decorator_list_next_index)
{
if *op_index == self.op_index {
self.decorator_list_next_index += 1;
return Some(OperationOrDecorator::Decorator(decorator));
}
}
if let Some(batch) = self.node.op_batches.get(self.batch_index) {
if let Some(operation) = batch.ops.get(self.op_index_in_batch) {
self.op_index_in_batch += 1;
self.op_index += 1;
Some(OperationOrDecorator::Operation(operation))
} else {
self.batch_index += 1;
self.op_index_in_batch = 0;
self.next()
}
} else {
None
}
}
}
fn batch_and_hash_ops(ops: Vec<Operation>) -> (Vec<OpBatch>, RpoDigest) {
let batches = batch_ops(ops);
let op_groups: Vec<Felt> = batches.iter().flat_map(|batch| batch.groups).collect();
let hash = hasher::hash_elements(&op_groups);
(batches, hash)
}
fn batch_ops(ops: Vec<Operation>) -> Vec<OpBatch> {
let mut batches = Vec::<OpBatch>::new();
let mut batch_acc = OpBatchAccumulator::new();
for op in ops {
if !batch_acc.can_accept_op(op) {
let batch = batch_acc.into_batch();
batch_acc = OpBatchAccumulator::new();
batches.push(batch);
}
batch_acc.add_op(op);
}
if !batch_acc.is_empty() {
let batch = batch_acc.into_batch();
batches.push(batch);
}
batches
}
#[cfg(debug_assertions)]
fn validate_decorators(operations: &[Operation], decorators: &DecoratorList) {
if !decorators.is_empty() {
for i in 0..(decorators.len() - 1) {
debug_assert!(decorators[i + 1].0 >= decorators[i].0, "unsorted decorators list");
}
debug_assert!(
operations.len() >= decorators.last().expect("empty decorators list").0,
"last op index in decorator list should be less than or equal to the number of ops"
);
}
}