Skip to content

Commit

Permalink
Merge pull request #1055 from 0xPolygonMiden/grjte-dyn-block
Browse files Browse the repository at this point in the history
Add Dyn block to MAST
  • Loading branch information
grjte authored Sep 6, 2023
2 parents 8e96c3e + 4911edd commit 1d380e1
Show file tree
Hide file tree
Showing 12 changed files with 372 additions and 5 deletions.
7 changes: 6 additions & 1 deletion core/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ pub enum Operation {
/// Marks the beginning of a function call.
Call,

/// Marks the beginning of a dynamic code block, where the target is specified by the stack.
Dyn,

/// Marks the beginning of a kernel call.
SysCall,

Expand Down Expand Up @@ -515,7 +518,7 @@ impl Operation {
Self::Loop => 0b0101_0101,
Self::Span => 0b0101_0110,
Self::Join => 0b0101_0111,
// <empty> => 0b0101_1000,
Self::Dyn => 0b0101_1000,
// <empty> => 0b0101_1001,
// <empty> => 0b0101_1010,
// <empty> => 0b0101_1011,
Expand Down Expand Up @@ -557,6 +560,7 @@ impl Operation {
| Self::Halt
| Self::Call
| Self::SysCall
| Self::Dyn
)
}
}
Expand All @@ -582,6 +586,7 @@ impl fmt::Display for Operation {
Self::Loop => write!(f, "loop"),
Self::Call => writeln!(f, "call"),
Self::SysCall => writeln!(f, "syscall"),
Self::Dyn => writeln!(f, "dyn"),
Self::Span => write!(f, "span"),
Self::End => write!(f, "end"),
Self::Repeat => write!(f, "repeat"),
Expand Down
64 changes: 64 additions & 0 deletions core/src/program/blocks/dyn_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use super::{fmt, Digest, Felt, Operation};

// CONSTANTS
// ================================================================================================

/// The Dyn block is represented by a constant, which is set to be the hash of two empty words
/// ([ZERO, ZERO, ZERO, ZERO]) with a domain value of `DYN_DOMAIN`, i.e.
/// hasher::merge_in_domain(&[Digest::default(), Digest::default()], Dyn::DOMAIN)
const DYN_CONSTANT: Digest = Digest::new([
Felt::new(8115106948140260551),
Felt::new(13491227816952616836),
Felt::new(15015806788322198710),
Felt::new(16575543461540527115),
]);

// Dyn BLOCK
// ================================================================================================
/// Block for dynamic code where the target is specified by the stack.
///
/// Executes the code block referenced by the hash on top of the stack. Fails if the body is
/// unavailable to the VM, or if the execution of the dynamically-specified code block fails.
///
/// The child of a Dyn block (the target specified by the stack) is always dynamic and does not
/// affect the representation of the Dyn block. Therefore all Dyn blocks are represented by the same
/// constant (rather than by unique hashes), which is computed as an RPO hash of two empty words
/// ([ZERO, ZERO, ZERO, ZERO]) with a domain value of `DYN_DOMAIN`.
#[derive(Clone, Debug)]
pub struct Dyn {}

impl Dyn {
// CONSTANTS
// --------------------------------------------------------------------------------------------
/// The domain of the Dyn block (used for control block hashing).
pub const DOMAIN: Felt = Felt::new(Operation::Dyn.op_code() as u64);

// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Returns a new [Dyn] block instantiated with the specified function body hash.
pub fn new() -> Self {
Self {}
}

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns a hash of this code block.
pub fn hash(&self) -> Digest {
DYN_CONSTANT
}
}

impl Default for Dyn {
fn default() -> Self {
Self::new()
}
}

impl fmt::Display for Dyn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Dyn")?;

Ok(())
}
}
11 changes: 11 additions & 0 deletions core/src/program/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use crate::DecoratorList;
use core::fmt;

mod call_block;
mod dyn_block;
mod join_block;
mod loop_block;
mod proxy_block;
mod span_block;
mod split_block;

pub use call_block::Call;
pub use dyn_block::Dyn;
pub use join_block::Join;
pub use loop_block::Loop;
pub use proxy_block::Proxy;
Expand All @@ -29,6 +31,7 @@ pub enum CodeBlock {
Split(Split),
Loop(Loop),
Call(Call),
Dyn(Dyn),
Proxy(Proxy),
}

Expand Down Expand Up @@ -71,6 +74,11 @@ impl CodeBlock {
Self::Call(Call::new_syscall(fn_hash))
}

/// TODO: add comments
pub fn new_dyn() -> Self {
Self::Dyn(Dyn::new())
}

/// TODO: add comments
pub fn new_proxy(code_hash: Digest) -> Self {
Self::Proxy(Proxy::new(code_hash))
Expand All @@ -92,6 +100,7 @@ impl CodeBlock {
CodeBlock::Split(block) => block.hash(),
CodeBlock::Loop(block) => block.hash(),
CodeBlock::Call(block) => block.hash(),
CodeBlock::Dyn(block) => block.hash(),
CodeBlock::Proxy(block) => block.hash(),
}
}
Expand All @@ -100,6 +109,7 @@ impl CodeBlock {
pub fn domain(&self) -> Felt {
match self {
CodeBlock::Call(block) => block.domain(),
CodeBlock::Dyn(_) => Dyn::DOMAIN,
CodeBlock::Join(_) => Join::DOMAIN,
CodeBlock::Loop(_) => Loop::DOMAIN,
CodeBlock::Span(_) => Span::DOMAIN,
Expand All @@ -117,6 +127,7 @@ impl fmt::Display for CodeBlock {
CodeBlock::Split(block) => write!(f, "{block}"),
CodeBlock::Loop(block) => write!(f, "{block}"),
CodeBlock::Call(block) => write!(f, "{block}"),
CodeBlock::Dyn(block) => write!(f, "{block}",),
CodeBlock::Proxy(block) => write!(f, "{block}"),
}
}
Expand Down
11 changes: 9 additions & 2 deletions core/src/program/tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use super::{Deserializable, Digest, Felt, Kernel, ProgramInfo, Serializable};
use crate::Word;
use super::{blocks::Dyn, Deserializable, Digest, Felt, Kernel, ProgramInfo, Serializable};
use crate::{chiplets::hasher, Word};
use proptest::prelude::*;
use rand_utils::prng_array;

#[test]
fn dyn_hash_is_correct() {
let expected_constant =
hasher::merge_in_domain(&[Digest::default(), Digest::default()], Dyn::DOMAIN);
assert_eq!(expected_constant, Dyn::new().hash());
}

proptest! {
#[test]
fn arbitrary_program_info_serialization_works(
Expand Down
Binary file added docs/src/assets/design/programs/dyn_block.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions docs/src/design/programs.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ When executing a *syscall* block, the VM does the following:

A *syscall* block does not have any children. Thus, it must be leaf node in the tree.


### Dyn block
A **dyn** block is used to describe a node whose target is specified dynamically via the stack. When the VM encounters a *dyn* block, it executes a program which hashes to the target specified by the top of the stack. Thus, it has a dynamic target rather than a hardcoded target. In order to execute a *dyn* block, the VM must be aware of a program with the hash value that is specified by the top of the stack. Otherwise, the execution fails.

![dyn_block](../assets/design/programs/dyn_block.png)

A *dyn* block must always have one (dynamically-specified) child. Thus, it cannot be a leaf node in the tree.

### Span block
A **span** block is used to describe a linear sequence of operations. When the VM encounters a *span* block, it breaks the sequence of operations into batches and groups according to the following rules:
* A group is represented by a single field element. Thus, assuming a single operation can be encoded using 7 bits, and assuming we are using a 64-bit field, a single group may encode up to 9 operations or a single immediate value.
Expand Down Expand Up @@ -118,5 +126,6 @@ Below we denote $hash$ to be an arithmetization-friendly hash function with $4$-
* The hash of a **loop** block is computed as $hash_{loop}(a, 0)$, where $a$ is a hash of a code block corresponding to the loop body.
* The hash of a **call** block is computed as $hash_{call}(a, 0)$, where $a$ is a hash of a program of which the VM is aware.
* The hash of a **syscall** block is computed as $hash_{syscall}(a, 0)$, where $a$ is a hash of a program belonging to the kernel against which the code was compiled.
* The hash of a **dyn** block is set to a constant, so it is the same for all *dyn* blocks. It does not depend on the hash of the dynamic child. This constant is computed as the RPO hash of two empty words (`[ZERO, ZERO, ZERO, ZERO]`) using a domain value of `DYN_DOMAIN`, where `DYN_DOMAIN` is the op code of the `Dyn` operation.
* The hash of a **span** block is computed as $hash(a_1, ..., a_k)$, where $a_i$ is the $i$th batch of operations in the *span* block. Each batch of operations is defined as containing $8$ field elements, and thus, hashing a $k$-batch *span* block requires $k$ absorption steps.
* In cases when the number of operations is insufficient to fill the last batch entirely, `NOOPs` are appended to the end of the last batch to ensure that the number of operations in the batch is always equal to $8$.
2 changes: 2 additions & 0 deletions processor/src/decoder/block_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ impl BlockInfo {
BlockType::Split => 1,
BlockType::Loop(is_entered) => u32::from(is_entered),
BlockType::Call => 1,
BlockType::Dyn => 1,
BlockType::SysCall => 1,
BlockType::Span => 0,
}
Expand Down Expand Up @@ -202,6 +203,7 @@ pub enum BlockType {
Split,
Loop(bool), // internal value set to false if the loop is never entered
Call,
Dyn,
SysCall,
Span,
}
51 changes: 50 additions & 1 deletion processor/src/decoder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{
AdviceProvider, Call, ColMatrix, ExecutionError, Felt, FieldElement, Join, Loop, OpBatch,
AdviceProvider, Call, ColMatrix, Dyn, ExecutionError, Felt, FieldElement, Join, Loop, OpBatch,
Operation, Process, Span, Split, StarkField, Vec, Word, EMPTY_WORD, MIN_TRACE_LEN, ONE,
OP_BATCH_SIZE, ZERO,
};
Expand Down Expand Up @@ -241,6 +241,35 @@ where
self.execute_op(Operation::Noop)
}

// DYN BLOCK
// --------------------------------------------------------------------------------------------

/// Starts decoding of a DYN block.
pub(super) fn start_dyn_block(
&mut self,
block: &Dyn,
dyn_hash: Word,
) -> Result<(), ExecutionError> {
let addr =
self.chiplets
.hash_control_block(EMPTY_WORD, EMPTY_WORD, Dyn::DOMAIN, block.hash());

self.decoder.start_dyn(dyn_hash, addr);
self.execute_op(Operation::Noop)
}

/// Ends decoding of a DYN block.
pub(super) fn end_dyn_block(&mut self, block: &Dyn) -> Result<(), ExecutionError> {
// this appends a row with END operation to the decoder trace. when the END operation is
// executed the rest of the VM state does not change
self.decoder.end_control_block(block.hash().into());

// send the end of control block to the chiplets bus to handle the final hash request.
self.chiplets.read_hash_result();

self.execute_op(Operation::Noop)
}

// SPAN BLOCK
// --------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -520,6 +549,26 @@ impl Decoder {
self.debug_info.append_operation(Operation::SysCall);
}

/// Starts decoding of a DYN block.
///
/// This pushes a block with ID=addr onto the block stack and appends execution of a DYN
/// operation to the trace.
pub fn start_dyn(&mut self, dyn_hash: Word, addr: Felt) {
// get the current clock cycle here (before the trace table is updated)
let clk = self.trace_len() as u32;

// push DYN block info onto the block stack and append a DYN row to the execution trace
let parent_addr = self.block_stack.push(addr, BlockType::Dyn, None);
self.trace.append_block_start(parent_addr, Operation::Dyn, dyn_hash, [ZERO; 4]);

// mark this cycle as the cycle at which a new DYN block began execution (this affects
// block stack and block hash tables). A DYN block has no children but points to the hash
// provided on the stack.
self.aux_hints.block_started(clk, self.block_stack.peek(), Some(dyn_hash), None);

self.debug_info.append_operation(Operation::Dyn);
}

/// Ends decoding of a control block (i.e., a non-SPAN block).
///
/// This appends an execution of an END operation to the trace. The top block on the block
Expand Down
Loading

0 comments on commit 1d380e1

Please sign in to comment.