use alloc::{boxed::Box, string::String, vec::Vec};
use core::{fmt, iter::repeat_n};
use crate::{
Felt, Word, ZERO,
chiplets::hasher,
crypto::hash::Blake3_256,
mast::{
DecoratedLinksIter, DecoratedOpLink, DecoratorId, DecoratorStore, MastForest,
MastForestError, MastNode, MastNodeFingerprint, MastNodeId,
},
operations::{DecoratorList, Operation},
prettier::PrettyPrint,
utils::LookupByIdx,
};
mod op_batch;
pub use op_batch::OpBatch;
use op_batch::OpBatchAccumulator;
pub(crate) use op_batch::collect_immediate_placements;
use super::{MastForestContributor, MastNodeExt};
#[cfg(any(test, feature = "arbitrary"))]
pub mod arbitrary;
#[cfg(test)]
mod tests;
pub const GROUP_SIZE: usize = 9;
pub const BATCH_SIZE: usize = 8;
const _: [(); 1] = [(); ((BATCH_SIZE & (BATCH_SIZE - 1)) == 0) as usize];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BasicBlockNode {
op_batches: Vec<OpBatch>,
digest: Word,
decorators: DecoratorStore,
}
impl BasicBlockNode {
pub const DOMAIN: Felt = ZERO;
}
impl BasicBlockNode {
#[cfg(any(test, feature = "arbitrary"))]
pub(crate) fn new_owned_with_decorators(
operations: Vec<Operation>,
decorators: DecoratorList,
) -> Result<Self, MastForestError> {
if operations.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
#[cfg(debug_assertions)]
validate_decorators(operations.len(), &decorators);
let (op_batches, digest) = batch_and_hash_ops(operations);
let reflowed_decorators = BasicBlockNode::adjust_decorators(decorators, &op_batches);
Ok(Self {
op_batches,
digest,
decorators: DecoratorStore::Owned {
decorators: reflowed_decorators,
before_enter: Vec::new(),
after_exit: Vec::new(),
},
})
}
pub fn adjust_decorators(decorators: DecoratorList, op_batches: &[OpBatch]) -> DecoratorList {
let raw2pad = RawToPaddedPrefix::new(op_batches);
decorators
.into_iter()
.map(|(raw_idx, dec_id)| (raw_idx + raw2pad[raw_idx], dec_id))
.collect()
}
pub fn adjust_asm_op_indices<T: Copy>(
asm_ops: Vec<(usize, T)>,
op_batches: &[OpBatch],
) -> Vec<(usize, T)> {
let raw2pad = RawToPaddedPrefix::new(op_batches);
asm_ops
.into_iter()
.map(|(raw_idx, id)| {
let padded = raw_idx + raw2pad[raw_idx];
(padded, id)
})
.collect()
}
}
impl BasicBlockNode {
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 indexed_decorator_iter<'a>(
&'a self,
forest: &'a MastForest,
) -> DecoratorOpLinkIterator<'a> {
match &self.decorators {
DecoratorStore::Owned { decorators, .. } => {
DecoratorOpLinkIterator::from_slice(decorators)
},
DecoratorStore::Linked { id } => {
let has_decorators = forest
.decorator_links_for_node(*id)
.map(|links| links.into_iter().next().is_some())
.unwrap_or(false);
if !has_decorators {
return DecoratorOpLinkIterator::from_slice(&[]);
}
let view = forest.decorator_links_for_node(*id).expect(
"linked node decorators should be available; forest may be inconsistent",
);
DecoratorOpLinkIterator::from_linked(view.into_iter())
},
}
}
pub fn raw_decorator_iter<'a>(
&'a self,
forest: &'a MastForest,
) -> RawDecoratorOpLinkIterator<'a> {
match &self.decorators {
DecoratorStore::Owned { decorators, before_enter, after_exit } => {
RawDecoratorOpLinkIterator::from_slice_iters(
before_enter,
decorators,
after_exit,
&self.op_batches,
)
},
DecoratorStore::Linked { id } => {
#[cfg(debug_assertions)]
self.verify_node_in_forest(forest);
let has_decorators = forest
.decorator_links_for_node(*id)
.map(|links| links.into_iter().next().is_some())
.unwrap_or(false);
if !has_decorators {
let before_enter = forest.before_enter_decorators(*id);
let after_exit = forest.after_exit_decorators(*id);
return RawDecoratorOpLinkIterator::from_slice_iters(
before_enter,
&[],
after_exit,
&self.op_batches,
);
}
let view = forest.decorator_links_for_node(*id).expect(
"linked node decorators should be available; forest may be inconsistent",
);
let before_enter = forest.before_enter_decorators(*id);
let after_exit = forest.after_exit_decorators(*id);
RawDecoratorOpLinkIterator::from_linked(
before_enter,
view.into_iter(),
after_exit,
&self.op_batches,
)
},
}
}
pub fn raw_op_indexed_decorators(&self, forest: &MastForest) -> Vec<(usize, DecoratorId)> {
match &self.decorators {
DecoratorStore::Owned { decorators, .. } => {
RawDecoratorOpLinkIterator::from_slice_iters(&[], decorators, &[], &self.op_batches)
.collect()
},
DecoratorStore::Linked { id } => {
let pad2raw = PaddedToRawPrefix::new(self.op_batches());
match forest.decorator_links_for_node(*id) {
Ok(links) => links
.into_iter()
.map(|(padded_idx, dec_id)| {
let raw_idx = padded_idx - pad2raw[padded_idx];
(raw_idx, dec_id)
})
.collect(),
Err(_) => Vec::new(), }
},
}
}
pub fn operations(&self) -> impl Iterator<Item = &Operation> {
self.op_batches.iter().flat_map(|batch| batch.ops())
}
pub fn raw_operations(&self) -> impl Iterator<Item = &Operation> {
self.op_batches.iter().flat_map(|batch| batch.raw_ops())
}
pub fn num_operations_and_decorators(&self, forest: &MastForest) -> u32 {
let num_ops: usize = self.num_operations() as usize;
let num_decorators = match &self.decorators {
DecoratorStore::Owned { decorators, .. } => decorators.len(),
DecoratorStore::Linked { id } => {
#[cfg(debug_assertions)]
self.verify_node_in_forest(forest);
forest
.decorator_links_for_node(*id)
.map(|links| links.into_iter().count())
.unwrap_or(0)
},
};
(num_ops + num_decorators)
.try_into()
.expect("basic block contains more than 2^32 operations and decorators")
}
fn iter<'a>(
&'a self,
forest: &'a MastForest,
) -> impl Iterator<Item = OperationOrDecorator<'a>> + 'a {
OperationOrDecoratorIterator::new_with_forest(self, forest)
}
#[cfg(test)]
pub fn semantic_eq(&self, other: &BasicBlockNode, forest: &MastForest) -> bool {
let self_ops: Vec<_> = self.operations().collect();
let other_ops: Vec<_> = other.operations().collect();
if self_ops != other_ops {
return false;
}
if self.before_enter(forest) != other.before_enter(forest) {
return false;
}
if self.after_exit(forest) != other.after_exit(forest) {
return false;
}
let self_decorators: Vec<_> = self.indexed_decorator_iter(forest).collect();
let other_decorators: Vec<_> = other.indexed_decorator_iter(forest).collect();
if self_decorators != other_decorators {
return false;
}
true
}
pub fn linked_id(&self) -> Option<MastNodeId> {
self.decorators.linked_id()
}
}
impl BasicBlockNode {
pub fn validate_batch_invariants(&self) -> Result<(), String> {
self.validate_power_of_two_groups()?;
self.validate_batch_structure()?;
self.validate_no_immediate_endings()?;
self.validate_immediate_commitment()?;
self.validate_padding_semantics()?;
Ok(())
}
fn validate_power_of_two_groups(&self) -> Result<(), String> {
for (batch_idx, batch) in self.op_batches.iter().enumerate() {
let num_groups = batch.num_groups();
if batch_idx + 1 < self.op_batches.len() {
if num_groups != BATCH_SIZE {
return Err(format!(
"Batch {}: {} groups is not full batch size {}",
batch_idx, num_groups, BATCH_SIZE
));
}
} else if !num_groups.is_power_of_two() {
return Err(format!(
"Batch {}: {} groups is not power of two",
batch_idx, num_groups
));
}
}
Ok(())
}
fn validate_no_immediate_endings(&self) -> Result<(), String> {
for (batch_idx, batch) in self.op_batches.iter().enumerate() {
let num_groups = batch.num_groups();
let indptr = batch.indptr();
let ops = batch.ops();
for group_idx in 0..num_groups {
let group_start = indptr[group_idx];
let group_end = indptr[group_idx + 1];
if group_start == group_end {
continue;
}
let group_ops = &ops[group_start..group_end];
let is_last_group = group_idx == num_groups - 1;
if is_last_group {
for (op_idx, op) in group_ops.iter().enumerate() {
if op.imm_value().is_some() {
return Err(format!(
"Batch {}, group {}: operation at index {} requires immediate value, but this is the last group in batch",
batch_idx, group_idx, op_idx
));
}
}
} else {
if let Some(last_op) = group_ops.last()
&& last_op.imm_value().is_some()
{
return Err(format!(
"Batch {}, group {}: ends with operation requiring immediate value",
batch_idx, group_idx
));
}
}
}
}
Ok(())
}
fn validate_batch_structure(&self) -> Result<(), String> {
for (batch_idx, batch) in self.op_batches.iter().enumerate() {
if batch.num_groups() > BATCH_SIZE {
return Err(format!(
"Batch {}: num_groups {} exceeds maximum {}",
batch_idx,
batch.num_groups(),
BATCH_SIZE
));
}
let indptr = batch.indptr();
let ops = batch.ops();
for i in 0..indptr.len() - 1 {
if indptr[i] > indptr[i + 1] {
return Err(format!(
"Batch {}: indptr[{}] {} > indptr[{}] {} - full array not monotonic (required for serialization)",
batch_idx,
i,
indptr[i],
i + 1,
indptr[i + 1]
));
}
}
let ops_len = ops.len();
if indptr[indptr.len() - 1] != ops_len {
return Err(format!(
"Batch {}: final indptr value {} doesn't match ops.len() {}",
batch_idx,
indptr[indptr.len() - 1],
ops_len
));
}
for group_idx in 0..batch.num_groups() {
let group_start = indptr[group_idx];
let group_end = indptr[group_idx + 1];
let group_size = group_end - group_start;
if group_size > GROUP_SIZE {
return Err(format!(
"Batch {}, group {}: contains {} operations, exceeds maximum {}",
batch_idx, group_idx, group_size, GROUP_SIZE
));
}
}
}
Ok(())
}
fn validate_immediate_commitment(&self) -> Result<(), String> {
for (batch_idx, batch) in self.op_batches.iter().enumerate() {
let num_groups = batch.num_groups();
let indptr = batch.indptr();
let ops = batch.ops();
let groups = batch.groups();
let mut immediate_slots = [false; BATCH_SIZE];
for group_idx in 0..num_groups {
let group_start = indptr[group_idx];
let group_end = indptr[group_idx + 1];
if group_start == group_end {
continue;
}
let mut group_value: u64 = 0;
for (local_op_idx, op) in ops[group_start..group_end].iter().enumerate() {
let opcode = op.op_code() as u64;
group_value |= opcode << (Operation::OP_BITS * local_op_idx);
}
if groups[group_idx] != Felt::new(group_value) {
return Err(format!(
"Batch {}, group {}: committed opcode group does not match operations",
batch_idx, group_idx
));
}
let (placements, _next_group_idx) = collect_immediate_placements(
ops,
indptr,
group_idx,
BATCH_SIZE,
Some(num_groups),
)
.map_err(|err| format!("Batch {}: {}", batch_idx, err))?;
for (imm_group_idx, imm_value) in placements {
if groups[imm_group_idx] != imm_value {
return Err(format!(
"Batch {}: push immediate value mismatch at index {}",
batch_idx, imm_group_idx
));
}
immediate_slots[imm_group_idx] = true;
}
}
for group_idx in 0..num_groups {
if indptr[group_idx] == indptr[group_idx + 1]
&& !immediate_slots[group_idx]
&& groups[group_idx] != ZERO
{
return Err(format!(
"Batch {}, group {}: empty group must be zero",
batch_idx, group_idx
));
}
}
}
Ok(())
}
fn validate_padding_semantics(&self) -> Result<(), String> {
for (batch_idx, batch) in self.op_batches.iter().enumerate() {
batch
.validate_padding_semantics()
.map_err(|err| format!("Batch {}: {}", batch_idx, err))?;
}
Ok(())
}
}
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 }
}
}
impl MastNodeExt for BasicBlockNode {
fn digest(&self) -> Word {
self.digest
}
fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
match &self.decorators {
DecoratorStore::Owned { before_enter, .. } => before_enter,
DecoratorStore::Linked { id } => {
#[cfg(debug_assertions)]
self.verify_node_in_forest(forest);
forest.before_enter_decorators(*id)
},
}
}
fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
match &self.decorators {
DecoratorStore::Owned { after_exit, .. } => after_exit,
DecoratorStore::Linked { id } => {
#[cfg(debug_assertions)]
self.verify_node_in_forest(forest);
forest.after_exit_decorators(*id)
},
}
}
fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
Box::new(BasicBlockNode::to_display(self, mast_forest))
}
fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
Box::new(BasicBlockNode::to_pretty_print(self, mast_forest))
}
fn has_children(&self) -> bool {
false
}
fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
}
fn for_each_child<F>(&self, _f: F)
where
F: FnMut(MastNodeId),
{
}
fn domain(&self) -> Felt {
Self::DOMAIN
}
type Builder = BasicBlockNodeBuilder;
fn to_builder(self, forest: &MastForest) -> Self::Builder {
let (padded_decorators, before_enter, after_exit) = match self.decorators {
DecoratorStore::Owned { decorators, before_enter, after_exit } => {
(decorators, before_enter, after_exit)
},
DecoratorStore::Linked { id } => {
let padded_decorators: DecoratorList = forest
.debug_info
.decorator_links_for_node(id)
.expect("node must exist in forest")
.into_iter()
.collect();
let before_enter = forest.before_enter_decorators(id).to_vec();
let after_exit = forest.after_exit_decorators(id).to_vec();
(padded_decorators, before_enter, after_exit)
},
};
BasicBlockNodeBuilder::from_op_batches(self.op_batches, padded_decorators, self.digest)
.with_before_enter(before_enter)
.with_after_exit(after_exit)
}
#[cfg(debug_assertions)]
fn verify_node_in_forest(&self, forest: &MastForest) {
if let DecoratorStore::Linked { id } = &self.decorators {
let self_ptr = self as *const Self;
let forest_node = &forest.nodes[*id];
let forest_node_ptr = match forest_node {
MastNode::Block(block_node) => block_node as *const BasicBlockNode as *const (),
_ => panic!("Node type mismatch at {:?}", id),
};
let self_as_void = self_ptr as *const ();
debug_assert_eq!(
self_as_void, forest_node_ptr,
"Node pointer mismatch: expected node at {:?} to be self",
id
);
}
}
}
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(self.mast_forest)
.map(|op_or_dec| match op_or_dec {
OperationOrDecorator::Operation(op) => op.render(),
OperationOrDecorator::Decorator(decorator_id) => {
self.mast_forest.decorator_by_id(decorator_id)
.map(|decorator| decorator.render())
.unwrap_or_else(|| const_text("<invalid_decorator_id>"))
},
})
.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(self.mast_forest)
.map(|op_or_dec| match op_or_dec {
OperationOrDecorator::Operation(op) => op.render(),
OperationOrDecorator::Decorator(decorator_id) => {
self.mast_forest.decorator_by_id(decorator_id)
.map(|decorator| decorator.render())
.unwrap_or_else(|| const_text("<invalid_decorator_id>"))
},
})
.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 {
self.pretty_print(f)
}
}
enum OpIndexed<'a> {
Slice(core::slice::Iter<'a, (usize, DecoratorId)>),
Linked(DecoratedLinksIter<'a>),
}
pub struct DecoratorOpLinkIterator<'a>(OpIndexed<'a>);
impl<'a> DecoratorOpLinkIterator<'a> {
pub fn from_slice(decorators: &'a [DecoratedOpLink]) -> Self {
Self(OpIndexed::Slice(decorators.iter()))
}
pub fn from_linked(decorators: DecoratedLinksIter<'a>) -> Self {
Self(OpIndexed::Linked(decorators.into_iter()))
}
}
impl<'a> Iterator for DecoratorOpLinkIterator<'a> {
type Item = (usize, DecoratorId);
fn next(&mut self) -> Option<Self::Item> {
match &mut self.0 {
OpIndexed::Slice(slice_iter) => slice_iter.next().copied(),
OpIndexed::Linked(linked_iter) => linked_iter.next(),
}
}
}
impl<'a> ExactSizeIterator for DecoratorOpLinkIterator<'a> {
#[inline]
fn len(&self) -> usize {
match &self.0 {
OpIndexed::Slice(slice_iter) => slice_iter.len(),
OpIndexed::Linked(linked_iter) => linked_iter.len(),
}
}
}
enum Segment {
Before,
Middle,
After,
Done,
}
pub struct RawDecoratorOpLinkIterator<'a> {
before: core::slice::Iter<'a, DecoratorId>,
middle: RawMid<'a>,
after: core::slice::Iter<'a, DecoratorId>,
pad2raw: PaddedToRawPrefix, total_raw_ops: usize, seg: Segment,
}
enum RawMid<'a> {
Slice(core::slice::Iter<'a, (usize, DecoratorId)>),
Linked(DecoratedLinksIter<'a>),
}
impl<'a> RawDecoratorOpLinkIterator<'a> {
pub fn from_slice_iters(
before_enter: &'a [DecoratorId],
decorators: &'a [(usize, DecoratorId)], after_exit: &'a [DecoratorId],
op_batches: &'a [OpBatch],
) -> Self {
let pad2raw = PaddedToRawPrefix::new(op_batches);
let raw2pad = RawToPaddedPrefix::new(op_batches);
let total_raw_ops = raw2pad.raw_ops();
Self {
before: before_enter.iter(),
middle: RawMid::Slice(decorators.iter()),
after: after_exit.iter(),
pad2raw,
total_raw_ops,
seg: Segment::Before,
}
}
pub fn from_linked(
before_enter: &'a [DecoratorId],
decorators: DecoratedLinksIter<'a>,
after_exit: &'a [DecoratorId],
op_batches: &'a [OpBatch],
) -> Self {
let pad2raw = PaddedToRawPrefix::new(op_batches);
let raw2pad = RawToPaddedPrefix::new(op_batches);
let total_raw_ops = raw2pad.raw_ops();
Self {
before: before_enter.iter(),
middle: RawMid::Linked(decorators.into_iter()),
after: after_exit.iter(),
pad2raw,
total_raw_ops,
seg: Segment::Before,
}
}
fn middle_next(&mut self) -> Option<(usize, DecoratorId)> {
match &mut self.middle {
RawMid::Slice(slice_iter) => slice_iter.next().copied(),
RawMid::Linked(linked_iter) => linked_iter.next(),
}
}
}
impl<'a> Iterator for RawDecoratorOpLinkIterator<'a> {
type Item = (usize, DecoratorId);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.seg {
Segment::Before => {
if let Some(&id) = self.before.next() {
return Some((0, id));
}
self.seg = Segment::Middle;
},
Segment::Middle => {
if let Some((padded_idx, id)) = self.middle_next() {
let raw_idx = padded_idx - self.pad2raw[padded_idx];
return Some((raw_idx, id));
}
self.seg = Segment::After;
},
Segment::After => {
if let Some(&id) = self.after.next() {
return Some((self.total_raw_ops, id));
}
self.seg = Segment::Done;
},
Segment::Done => return None,
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OperationOrDecorator<'a> {
Operation(&'a Operation),
Decorator(DecoratorId),
}
struct OperationOrDecoratorIterator<'a> {
node: &'a BasicBlockNode,
forest: Option<&'a MastForest>,
before: core::slice::Iter<'a, DecoratorId>,
after: core::slice::Iter<'a, DecoratorId>,
batch_index: usize,
op_index_in_batch: usize,
op_index: usize,
decorator_list_next_index: usize,
seg: Segment,
}
impl<'a> OperationOrDecoratorIterator<'a> {
fn new_with_forest(node: &'a BasicBlockNode, forest: &'a MastForest) -> Self {
Self {
node,
forest: Some(forest),
before: node.before_enter(forest).iter(),
after: node.after_exit(forest).iter(),
batch_index: 0,
op_index_in_batch: 0,
op_index: 0,
decorator_list_next_index: 0,
seg: Segment::Before,
}
}
#[inline]
fn next_decorator_if_due(&mut self) -> Option<OperationOrDecorator<'a>> {
match &self.node.decorators {
DecoratorStore::Owned { decorators, .. } => {
if let Some((op_idx, deco)) = decorators.get(self.decorator_list_next_index)
&& *op_idx == self.op_index
{
self.decorator_list_next_index += 1;
Some(OperationOrDecorator::Decorator(*deco))
} else {
None
}
},
DecoratorStore::Linked { id } => {
if let Some(forest) = self.forest {
let decorator_ids = forest.decorator_indices_for_op(*id, self.op_index);
if self.decorator_list_next_index < decorator_ids.len() {
let decorator_id = decorator_ids[self.decorator_list_next_index];
self.decorator_list_next_index += 1;
Some(OperationOrDecorator::Decorator(decorator_id))
} else {
None
}
} else {
None
}
},
}
}
}
impl<'a> Iterator for OperationOrDecoratorIterator<'a> {
type Item = OperationOrDecorator<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.seg {
Segment::Before => {
if let Some(id) = self.before.next() {
return Some(OperationOrDecorator::Decorator(*id));
}
self.seg = Segment::Middle;
},
Segment::Middle => {
if let Some(d) = self.next_decorator_if_due() {
return Some(d);
}
if let Some(batch) = self.node.op_batches.get(self.batch_index) {
if let Some(op) = batch.ops.get(self.op_index_in_batch) {
self.op_index_in_batch += 1;
self.op_index += 1;
self.decorator_list_next_index = 0;
return Some(OperationOrDecorator::Operation(op));
}
self.batch_index += 1;
self.op_index_in_batch = 0;
} else {
self.seg = Segment::After;
}
},
Segment::After => {
if let Some(id) = self.after.next() {
return Some(OperationOrDecorator::Decorator(*id));
}
self.seg = Segment::Done;
},
Segment::Done => return None,
}
}
}
}
#[cfg(debug_assertions)]
pub(crate) fn validate_decorators(operations_len: usize, 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"
);
}
}
#[derive(Debug, Clone)]
pub struct RawToPaddedPrefix(Vec<usize>);
impl RawToPaddedPrefix {
pub fn new(op_batches: &[OpBatch]) -> Self {
let mut v = Vec::new();
let mut pads_so_far = 0usize;
for b in op_batches {
let n = b.num_groups();
let indptr = b.indptr();
let padding = b.padding();
for g in 0..n {
let group_len = indptr[g + 1] - indptr[g];
let has_pad = padding[g] as usize;
let raw_in_g = group_len - has_pad;
v.extend(repeat_n(pads_so_far, raw_in_g));
pads_so_far += has_pad; }
}
v.push(pads_so_far);
RawToPaddedPrefix(v)
}
#[inline]
pub fn raw_ops(&self) -> usize {
self.0.len() - 1
}
}
impl core::ops::Index<usize> for RawToPaddedPrefix {
type Output = usize;
#[inline]
fn index(&self, idx: usize) -> &Self::Output {
&self.0[idx]
}
}
#[derive(Debug, Clone)]
pub struct PaddedToRawPrefix(Vec<usize>);
impl PaddedToRawPrefix {
pub fn new(op_batches: &[OpBatch]) -> Self {
let padded_ops = op_batches
.iter()
.map(|b| {
let n = b.num_groups();
let indptr = b.indptr();
indptr[1..=n]
.iter()
.zip(&indptr[..n])
.map(|(end, start)| end - start)
.sum::<usize>()
})
.sum::<usize>();
let mut v = Vec::with_capacity(padded_ops + 1);
let mut pads_so_far = 0usize;
for b in op_batches {
let n = b.num_groups();
let indptr = b.indptr();
let padding = b.padding();
for g in 0..n {
let group_len = indptr[g + 1] - indptr[g];
let has_pad = padding[g] as usize;
let raw_in_g = group_len - has_pad;
v.extend(repeat_n(pads_so_far, raw_in_g));
if has_pad == 1 {
v.push(pads_so_far);
pads_so_far += 1; }
}
}
v.push(pads_so_far);
PaddedToRawPrefix(v)
}
}
impl core::ops::Index<usize> for PaddedToRawPrefix {
type Output = usize;
#[inline]
fn index(&self, idx: usize) -> &Self::Output {
&self.0[idx]
}
}
fn batch_and_hash_ops(ops: Vec<Operation>) -> (Vec<OpBatch>, Word) {
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
}
#[derive(Debug)]
enum OperationData {
Raw {
operations: Vec<Operation>,
decorators: DecoratorList,
},
Batched {
op_batches: Vec<OpBatch>,
decorators: DecoratorList,
},
}
#[derive(Debug)]
pub struct BasicBlockNodeBuilder {
operation_data: OperationData,
before_enter: Vec<DecoratorId>,
after_exit: Vec<DecoratorId>,
digest: Option<Word>,
}
impl BasicBlockNodeBuilder {
pub fn new(operations: Vec<Operation>, decorators: DecoratorList) -> Self {
Self {
operation_data: OperationData::Raw { operations, decorators },
before_enter: Vec::new(),
after_exit: Vec::new(),
digest: None,
}
}
pub(crate) fn from_op_batches(
op_batches: Vec<OpBatch>,
decorators: DecoratorList,
digest: Word,
) -> Self {
Self {
operation_data: OperationData::Batched { op_batches, decorators },
before_enter: Vec::new(),
after_exit: Vec::new(),
digest: Some(digest),
}
}
pub fn build(self) -> Result<BasicBlockNode, MastForestError> {
let (op_batches, digest, padded_decorators) = match self.operation_data {
OperationData::Raw { operations, decorators } => {
if operations.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
#[cfg(debug_assertions)]
validate_decorators(operations.len(), &decorators);
let (op_batches, computed_digest) = batch_and_hash_ops(operations);
let padded_decorators = BasicBlockNode::adjust_decorators(decorators, &op_batches);
let digest = self.digest.unwrap_or(computed_digest);
(op_batches, digest, padded_decorators)
},
OperationData::Batched { op_batches, decorators } => {
if op_batches.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
let digest = self.digest.expect("digest must be set for batched operations");
(op_batches, digest, decorators)
},
};
Ok(BasicBlockNode {
op_batches,
digest,
decorators: DecoratorStore::Owned {
decorators: padded_decorators,
before_enter: self.before_enter.clone(),
after_exit: self.after_exit.clone(),
},
})
}
pub(in crate::mast) fn add_to_forest_relaxed(
self,
forest: &mut MastForest,
) -> Result<MastNodeId, MastForestError> {
let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
let (op_batches, digest) = match self.operation_data {
OperationData::Raw { operations, decorators: _ } => {
if operations.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
let (op_batches, computed_digest) = batch_and_hash_ops(operations);
let digest = self.digest.unwrap_or(computed_digest);
(op_batches, digest)
},
OperationData::Batched { op_batches, decorators: _ } => {
if op_batches.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
let digest = self.digest.expect("digest must be set for batched operations");
(op_batches, digest)
},
};
let node_id = forest
.nodes
.push(MastNode::Block(BasicBlockNode {
op_batches,
digest,
decorators: DecoratorStore::Linked { id: future_node_id },
}))
.map_err(|_| MastForestError::TooManyNodes)?;
Ok(node_id)
}
}
impl MastForestContributor for BasicBlockNodeBuilder {
fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
let (op_batches, digest, padded_decorators) = match self.operation_data {
OperationData::Raw { operations, decorators } => {
if operations.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
#[cfg(debug_assertions)]
validate_decorators(operations.len(), &decorators);
let (op_batches, computed_digest) = batch_and_hash_ops(operations);
let digest = self.digest.unwrap_or(computed_digest);
let padded_decorators = BasicBlockNode::adjust_decorators(decorators, &op_batches);
(op_batches, digest, padded_decorators)
},
OperationData::Batched { op_batches, decorators } => {
if op_batches.is_empty() {
return Err(MastForestError::EmptyBasicBlock);
}
let digest = self.digest.expect("digest must be set for batched operations");
(op_batches, digest, decorators)
},
};
forest
.debug_info
.register_op_indexed_decorators(future_node_id, padded_decorators)
.map_err(MastForestError::DecoratorError)?;
forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
let node_id = forest
.nodes
.push(MastNode::Block(BasicBlockNode {
op_batches,
digest,
decorators: DecoratorStore::Linked { id: future_node_id },
}))
.map_err(|_| MastForestError::TooManyNodes)?;
Ok(node_id)
}
fn fingerprint_for_node(
&self,
forest: &MastForest,
_hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
) -> Result<MastNodeFingerprint, MastForestError> {
let (op_batches, digest, raw_decorators) = match &self.operation_data {
OperationData::Raw { operations, decorators } => {
let (op_batches, computed_digest) = batch_and_hash_ops(operations.clone());
let digest = self.digest.unwrap_or(computed_digest);
#[cfg(debug_assertions)]
{
validate_decorators(operations.len(), decorators);
}
(op_batches, digest, decorators.clone())
},
OperationData::Batched { op_batches, decorators } => {
let digest = self.digest.expect("digest must be set for batched operations");
let pad2raw = PaddedToRawPrefix::new(op_batches);
let raw_decorators: Vec<(usize, DecoratorId)> = decorators
.iter()
.map(|(padded_idx, decorator_id)| {
let raw_idx = padded_idx - pad2raw[*padded_idx];
(raw_idx, *decorator_id)
})
.collect();
(op_batches.clone(), digest, raw_decorators)
},
};
let before_enter_bytes: Vec<[u8; 32]> = self
.before_enter
.iter()
.map(|&id| *forest[id].fingerprint().as_bytes())
.collect();
let adjusted_decorators = raw_decorators;
let mut op_decorator_data = Vec::with_capacity(adjusted_decorators.len() * 33);
for (raw_op_idx, decorator_id) in &adjusted_decorators {
op_decorator_data.extend_from_slice(&raw_op_idx.to_le_bytes());
op_decorator_data.extend_from_slice(forest[*decorator_id].fingerprint().as_bytes());
}
let after_exit_bytes: Vec<[u8; 32]> =
self.after_exit.iter().map(|&id| *forest[id].fingerprint().as_bytes()).collect();
let mut assert_data = Vec::new();
for (op_idx, op) in op_batches.iter().flat_map(|batch| batch.ops()).enumerate() {
if let Operation::U32assert2(inner_value)
| Operation::Assert(inner_value)
| Operation::MpVerify(inner_value) = op
{
let op_idx: u32 = op_idx
.try_into()
.expect("there are more than 2^{32}-1 operations in basic block");
assert_data.push(op.op_code());
assert_data.extend_from_slice(&op_idx.to_le_bytes());
let inner_value = inner_value.as_canonical_u64();
assert_data.extend_from_slice(&inner_value.to_le_bytes());
}
}
let decorator_bytes_iter = before_enter_bytes
.iter()
.map(|bytes| bytes.as_slice())
.chain(core::iter::once(op_decorator_data.as_slice()))
.chain(after_exit_bytes.iter().map(|bytes| bytes.as_slice()))
.chain(core::iter::once(assert_data.as_slice()));
if self.before_enter.is_empty()
&& self.after_exit.is_empty()
&& adjusted_decorators.is_empty()
&& assert_data.is_empty()
{
Ok(MastNodeFingerprint::new(digest))
} else {
let decorator_root = Blake3_256::hash_iter(decorator_bytes_iter);
Ok(MastNodeFingerprint::with_decorator_root(digest, decorator_root))
}
}
fn remap_children(self, _remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
self
}
fn with_before_enter(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
self.before_enter = decorators.into();
self
}
fn with_after_exit(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
self.after_exit = decorators.into();
self
}
fn append_before_enter(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
self.before_enter.extend(decorators);
}
fn append_after_exit(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
self.after_exit.extend(decorators);
}
fn with_digest(mut self, digest: Word) -> Self {
self.digest = Some(digest);
self
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::prelude::Arbitrary for BasicBlockNodeBuilder {
type Parameters = super::arbitrary::BasicBlockNodeParams;
type Strategy = proptest::strategy::BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
use proptest::prelude::*;
use super::arbitrary::{decorator_id_strategy, op_non_control_sequence_strategy};
(op_non_control_sequence_strategy(params.max_ops_len),)
.prop_flat_map(move |(ops,)| {
let ops_len = ops.len().max(1); prop::collection::vec(
(0..ops_len, decorator_id_strategy(params.max_decorator_id_u32)),
0..=params.max_pairs,
)
.prop_map(move |mut decorators| {
decorators.sort_by_key(|(i, _)| *i);
(ops.clone(), decorators)
})
})
.prop_map(|(ops, decorators)| Self::new(ops, decorators))
.boxed()
}
}