Skip to content

Commit

Permalink
chore: added comments and section separators
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbinth committed Jul 21, 2024
1 parent f1c1282 commit ba78af3
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 278 deletions.
42 changes: 21 additions & 21 deletions core/src/mast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use miden_crypto::hash::rpo::RpoDigest;

mod node;
pub use node::{
get_span_op_group_count, BasicBlockNode, CallNode, DynNode, ExternalNode, JoinNode, LoopNode,
MastNode, OpBatch, OperationOrDecorator, SplitNode, OP_BATCH_SIZE, OP_GROUP_SIZE,
BasicBlockNode, CallNode, DynNode, ExternalNode, JoinNode, LoopNode, MastNode, OpBatch,
OperationOrDecorator, SplitNode, OP_BATCH_SIZE, OP_GROUP_SIZE,
};
use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};

Expand All @@ -18,14 +18,17 @@ mod tests;
// MAST FOREST
// ================================================================================================

/// Represents the types of errors that can occur when dealing with MAST forest.
#[derive(Debug, thiserror::Error)]
pub enum MastForestError {
#[error(
"invalid node count: MAST forest exceeds the maximum of {} nodes",
MastForest::MAX_NODES
)]
TooManyNodes,
/// Represents one or more procedures, represented as a collection of [`MastNode`]s.
///
/// A [`MastForest`] does not have an entrypoint, and hence is not executable. A [`crate::Program`]
/// can be built from a [`MastForest`] to specify an entrypoint.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MastForest {
/// All of the nodes local to the trees comprising the MAST forest.
nodes: Vec<MastNode>,

/// Roots of procedures defined within this MAST forest.
roots: Vec<MastNodeId>,
}

// ------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -174,15 +177,12 @@ impl Deserializable for MastNodeId {
// MAST FOREST ERROR
// ================================================================================================

/// Represents one or more procedures, represented as a collection of [`MastNode`]s.
///
/// A [`MastForest`] does not have an entrypoint, and hence is not executable. A [`crate::Program`]
/// can be built from a [`MastForest`] to specify an entrypoint.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MastForest {
/// All of the nodes local to the trees comprising the MAST forest.
nodes: Vec<MastNode>,

/// Roots of procedures defined within this MAST forest.
roots: Vec<MastNodeId>,
/// Represents the types of errors that can occur when dealing with MAST forest.
#[derive(Debug, thiserror::Error)]
pub enum MastForestError {
#[error(
"invalid node count: MAST forest exceeds the maximum of {} nodes",
MastForest::MAX_NODES
)]
TooManyNodes,
}
246 changes: 40 additions & 206 deletions core/src/mast/node/basic_block_node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use miden_crypto::{hash::rpo::RpoDigest, Felt, ZERO};
use miden_formatting::prettier::PrettyPrint;
use winter_utils::flatten_slice_elements;

use crate::{
chiplets::hasher, mast::MastForest, Decorator, DecoratorIterator, DecoratorList, Operation,
};
use crate::{chiplets::hasher, Decorator, DecoratorIterator, DecoratorList, Operation};

mod op_batch;
pub use op_batch::OpBatch;
use op_batch::OpBatchAccumulator;

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -111,23 +113,36 @@ impl BasicBlockNode {
// ------------------------------------------------------------------------------------------------
/// Public accessors
impl BasicBlockNode {
pub fn num_operations_and_decorators(&self) -> u32 {
let num_ops: usize = self.op_batches.iter().map(|batch| batch.ops().len()).sum();
let num_decorators = self.decorators.len();

(num_ops + num_decorators)
.try_into()
.expect("basic block contains more than 2^32 operations and decorators")
/// Returns a commitment to this basic block.
pub fn digest(&self) -> RpoDigest {
self.digest
}

/// Returns a reference to the operation batches in this basic block.
pub fn op_batches(&self) -> &[OpBatch] {
&self.op_batches
}

/// Returns an iterator over all operations and decorator, in the order in which they appear in
/// the program.
pub fn iter(&self) -> impl Iterator<Item = OperationOrDecorator> {
OperationOrDecoratorIterator::new(self)
/// Returns the total number of operation groups in this basic block.
///
/// Then number of operation groups is computed as follows:
/// - For all batches but the last one we set the number of groups to 8, regardless of the
/// actual number of groups in the batch. The reason for this is that when operation batches
/// are concatenated together each batch contributes 8 elements to the hash.
/// - For the last batch, we take the number of actual groups and round it up to the next power
/// of two. The reason for rounding is that the VM always executes a number of operation
/// groups which is a power of two.
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()
}

/// Returns a list of decorators in this basic block node.
///
/// Each decorator is accompanied by the operation index specifying the operation prior to
/// which the decorator should be executed.
pub fn decorators(&self) -> &DecoratorList {
&self.decorators
}

/// Returns a [`DecoratorIterator`] which allows us to iterate through the decorator list of
Expand All @@ -136,17 +151,20 @@ impl BasicBlockNode {
DecoratorIterator::new(&self.decorators)
}

/// Returns a list of decorators in this basic block node.
pub fn decorators(&self) -> &DecoratorList {
&self.decorators
}
/// Returns the total number of operations and decorators in this basic block.
pub fn num_operations_and_decorators(&self) -> u32 {
let num_ops: usize = self.op_batches.iter().map(|batch| batch.ops().len()).sum();
let num_decorators = self.decorators.len();

pub fn digest(&self) -> RpoDigest {
self.digest
(num_ops + num_decorators)
.try_into()
.expect("basic block contains more than 2^32 operations and decorators")
}

pub fn to_display<'a>(&'a self, _mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
self
/// Returns an iterator over all operations and decorator, in the order in which they appear in
/// the program.
pub fn iter(&self) -> impl Iterator<Item = OperationOrDecorator> {
OperationOrDecoratorIterator::new(self)
}
}

Expand Down Expand Up @@ -275,175 +293,6 @@ impl<'a> Iterator for OperationOrDecoratorIterator<'a> {
}
}

// OPERATION BATCH
// ================================================================================================

/// A batch of operations in a span block.
///
/// An operation batch consists of up to 8 operation groups, with each group containing up to 9
/// operations or a single immediate value.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OpBatch {
ops: Vec<Operation>,
groups: [Felt; BATCH_SIZE],
op_counts: [usize; BATCH_SIZE],
num_groups: usize,
}

impl OpBatch {
/// Returns a list of operations contained in this batch.
pub fn ops(&self) -> &[Operation] {
&self.ops
}

/// Returns a list of operation groups contained in this batch.
///
/// Each group is represented by a single field element.
pub fn groups(&self) -> &[Felt; BATCH_SIZE] {
&self.groups
}

/// Returns the number of non-decorator operations for each operation group.
///
/// Number of operations for groups containing immediate values is set to 0.
pub fn op_counts(&self) -> &[usize; BATCH_SIZE] {
&self.op_counts
}

/// Returns the number of groups in this batch.
pub fn num_groups(&self) -> usize {
self.num_groups
}
}

/// An accumulator used in construction of operation batches.
struct OpBatchAccumulator {
/// A list of operations in this batch, including decorators.
ops: Vec<Operation>,
/// Values of operation groups, including immediate values.
groups: [Felt; BATCH_SIZE],
/// Number of non-decorator operations in each operation group. Operation count for groups
/// with immediate values is set to 0.
op_counts: [usize; BATCH_SIZE],
/// Value of the currently active op group.
group: u64,
/// Index of the next opcode in the current group.
op_idx: usize,
/// index of the current group in the batch.
group_idx: usize,
// Index of the next free group in the batch.
next_group_idx: usize,
}

impl OpBatchAccumulator {
/// Returns a blank [OpBatchAccumulator].
pub fn new() -> Self {
Self {
ops: Vec::new(),
groups: [ZERO; BATCH_SIZE],
op_counts: [0; BATCH_SIZE],
group: 0,
op_idx: 0,
group_idx: 0,
next_group_idx: 1,
}
}

/// Returns true if this accumulator does not contain any operations.
pub fn is_empty(&self) -> bool {
self.ops.is_empty()
}

/// Returns true if this accumulator can accept the specified operation.
///
/// An accumulator may not be able accept an operation for the following reasons:
/// - There is no more space in the underlying batch (e.g., the 8th group of the batch already
/// contains 9 operations).
/// - There is no space for the immediate value carried by the operation (e.g., the 8th group is
/// only partially full, but we are trying to add a PUSH operation).
/// - The alignment rules require that the operation overflows into the next group, and if this
/// happens, there will be no space for the operation or its immediate value.
pub fn can_accept_op(&self, op: Operation) -> bool {
if op.imm_value().is_some() {
// an operation carrying an immediate value cannot be the last one in a group; so, we
// check if we need to move the operation to the next group. in either case, we need
// to make sure there is enough space for the immediate value as well.
if self.op_idx < GROUP_SIZE - 1 {
self.next_group_idx < BATCH_SIZE
} else {
self.next_group_idx + 1 < BATCH_SIZE
}
} else {
// check if there is space for the operation in the current group, or if there isn't,
// whether we can add another group
self.op_idx < GROUP_SIZE || self.next_group_idx < BATCH_SIZE
}
}

/// Adds the specified operation to this accumulator. It is expected that the specified
/// operation is not a decorator and that (can_accept_op())[OpBatchAccumulator::can_accept_op]
/// is called before this function to make sure that the specified operation can be added to
/// the accumulator.
pub fn add_op(&mut self, op: Operation) {
// if the group is full, finalize it and start a new group
if self.op_idx == GROUP_SIZE {
self.finalize_op_group();
}

// for operations with immediate values, we need to do a few more things
if let Some(imm) = op.imm_value() {
// since an operation with an immediate value cannot be the last one in a group, if
// the operation would be the last one in the group, we need to start a new group
if self.op_idx == GROUP_SIZE - 1 {
self.finalize_op_group();
}

// save the immediate value at the next group index and advance the next group pointer
self.groups[self.next_group_idx] = imm;
self.next_group_idx += 1;
}

// add the opcode to the group and increment the op index pointer
let opcode = op.op_code() as u64;
self.group |= opcode << (Operation::OP_BITS * self.op_idx);
self.ops.push(op);
self.op_idx += 1;
}

/// Convert the accumulator into an [OpBatch].
pub fn into_batch(mut self) -> OpBatch {
// make sure the last group gets added to the group array; we also check the op_idx to
// handle the case when a group contains a single NOOP operation.
if self.group != 0 || self.op_idx != 0 {
self.groups[self.group_idx] = Felt::new(self.group);
self.op_counts[self.group_idx] = self.op_idx;
}

OpBatch {
ops: self.ops,
groups: self.groups,
op_counts: self.op_counts,
num_groups: self.next_group_idx,
}
}

// HELPER METHODS
// --------------------------------------------------------------------------------------------

/// Saves the current group into the group array, advances current and next group pointers,
/// and resets group content.
fn finalize_op_group(&mut self) {
self.groups[self.group_idx] = Felt::new(self.group);
self.op_counts[self.group_idx] = self.op_idx;

self.group_idx = self.next_group_idx;
self.next_group_idx = self.group_idx + 1;

self.op_idx = 0;
self.group = 0;
}
}

// HELPER FUNCTIONS
// ================================================================================================

Expand Down Expand Up @@ -485,21 +334,6 @@ fn batch_ops(ops: Vec<Operation>) -> (Vec<OpBatch>, RpoDigest) {
(batches, hash)
}

/// Returns the total number of operation groups in a span defined by the provides list of
/// operation batches.
///
/// Then number of operation groups is computed as follows:
/// - For all batches but the last one we set the number of groups to 8, regardless of the actual
/// number of groups in the batch. The reason for this is that when operation batches are
/// concatenated together each batch contributes 8 elements to the hash.
/// - For the last batch, we take the number of actual batches and round it up to the next power of
/// two. The reason for rounding is that the VM always executes a number of operation groups which
/// is a power of two.
pub fn get_span_op_group_count(op_batches: &[OpBatch]) -> usize {
let last_batch_num_groups = op_batches.last().expect("no last group").num_groups();
(op_batches.len() - 1) * BATCH_SIZE + last_batch_num_groups.next_power_of_two()
}

/// Checks if a given decorators list is valid (only checked in debug mode)
/// - Assert the decorator list is in ascending order.
/// - Assert the last op index in decorator list is less than or equal to the number of operations.
Expand Down
Loading

0 comments on commit ba78af3

Please sign in to comment.