diff --git a/CHANGELOG.md b/CHANGELOG.md index 32eb70d36c..fc735bc046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Debug instructions can be enabled in the cli `run` command using `--debug` flag (#1502) - [BREAKING] ExecutionOptions::new constructor requires a boolean to explicitly set debug mode (#1502) - [BREAKING] The `run` and the `prove` commands in the cli will accept `--trace` flag instead of `--tracing` (#1502) +- [BREAKING] `DYN` operation now expects a memory address pointing to the procedure hash (#1535) +- [BREAKING] `DYNCALL` operation fixed, and now expects a memory address pointing to the procedure hash (#1535) #### Fixes diff --git a/air/src/constraints/stack/mod.rs b/air/src/constraints/stack/mod.rs index d1c5b8b72c..6a4d8f9085 100644 --- a/air/src/constraints/stack/mod.rs +++ b/air/src/constraints/stack/mod.rs @@ -290,8 +290,8 @@ trait EvaluationFrameExt { fn user_op_helper(&self, index: usize) -> E; /// Returns the value if the `h6` helper register in the decoder which is set to ONE if the - /// ending block is a `CALL` block. - fn is_call_end(&self) -> E; + /// ending block is a `CALL` or `DYNCALL` block. + fn is_call_or_dyncall_end(&self) -> E; /// Returns the value if the `h7` helper register in the decoder which is set to ONE if the /// ending block is a `SYSCALL` block. @@ -359,7 +359,7 @@ impl EvaluationFrameExt for &EvaluationFrame { } #[inline] - fn is_call_end(&self) -> E { + fn is_call_or_dyncall_end(&self) -> E { self.current()[DECODER_TRACE_OFFSET + IS_CALL_FLAG_COL_IDX] } diff --git a/air/src/constraints/stack/op_flags/mod.rs b/air/src/constraints/stack/op_flags/mod.rs index f0a8342583..caa8302c15 100644 --- a/air/src/constraints/stack/op_flags/mod.rs +++ b/air/src/constraints/stack/op_flags/mod.rs @@ -348,7 +348,9 @@ impl OpFlags { + degree7_op_flags[47] + degree7_op_flags[46] + split_loop_flag - + shift_left_on_end; + + shift_left_on_end + + degree5_op_flags[8] // DYN + + degree5_op_flags[12]; // DYNCALL left_shift_flags[2] = left_shift_flags[1] + left_change_1_flag; left_shift_flags[3] = @@ -398,9 +400,15 @@ impl OpFlags { // Flag if the stack has been shifted to the right. let right_shift = f011 + degree5_op_flags[11] + degree6_op_flags[4]; // PUSH; U32SPLIT - // Flag if the stack has been shifted to the left. - let left_shift = - f010 + add3_madd_flag + split_loop_flag + degree4_op_flags[5] + shift_left_on_end; + // Flag if the stack has been shifted to the left. Note that `DYNCALL` is not included in + // this flag even if it shifts the stack to the left. See `Opflags::left_shift()` for more + // information. + let left_shift = f010 + + add3_madd_flag + + split_loop_flag + + degree4_op_flags[5] + + shift_left_on_end + + degree5_op_flags[8]; // DYN // Flag if the current operation being executed is a control flow operation. // first row: SPAN, JOIN, SPLIT, LOOP @@ -923,6 +931,12 @@ impl OpFlags { self.degree4_op_flags[get_op_index(Operation::SysCall.op_code())] } + /// Operation Flag of DYNCALL operation. + #[inline(always)] + pub fn dyncall(&self) -> E { + self.degree5_op_flags[get_op_index(Operation::Dyncall.op_code())] + } + /// Operation Flag of END operation. #[inline(always)] pub fn end(&self) -> E { @@ -982,6 +996,11 @@ impl OpFlags { } /// Returns the flag when the stack operation shifts the flag to the left. + /// + /// Note that although `DYNCALL` shifts the entire stack, it is not included in this flag. This + /// is because this "aggregate left shift" flag is used in constraints related to the stack + /// helper columns, and `DYNCALL` uses them unconventionally. + /// /// Degree: 5 #[inline(always)] pub fn left_shift(&self) -> E { diff --git a/air/src/constraints/stack/overflow/mod.rs b/air/src/constraints/stack/overflow/mod.rs index 5e055eedd5..6cac64a6c9 100644 --- a/air/src/constraints/stack/overflow/mod.rs +++ b/air/src/constraints/stack/overflow/mod.rs @@ -65,7 +65,8 @@ pub fn enforce_constraints( /// - If the operation is a left shift op, then, depth should be decreased by 1 provided the /// existing depth of the stack is not 16. In the case of depth being 16, depth will not be /// updated. -/// - If the current op being executed is `CALL` or `SYSCALL`, then the depth should be reset to 16. +/// - If the current op being executed is `CALL`, `SYSCALL` or `DYNCALL`, then the depth should be +/// reset to 16. /// /// TODO: This skips the operation when `END` is exiting for a `CALL` or a `SYSCALL` block. It /// should be handled later in multiset constraints. @@ -77,13 +78,15 @@ pub fn enforce_stack_depth_constraints( let depth = frame.stack_depth(); let depth_next = frame.stack_depth_next(); - let call_or_syscall = op_flag.call() + op_flag.syscall(); - let call_or_syscall_end = op_flag.end() * (frame.is_call_end() + frame.is_syscall_end()); + let call_or_dyncall_or_syscall = op_flag.call() + op_flag.dyncall() + op_flag.syscall(); + let call_or_dyncall_or_syscall_end = + op_flag.end() * (frame.is_call_or_dyncall_end() + frame.is_syscall_end()); - let no_shift_part = (depth_next - depth) * (E::ONE - call_or_syscall - call_or_syscall_end); + let no_shift_part = (depth_next - depth) + * (E::ONE - call_or_dyncall_or_syscall - call_or_dyncall_or_syscall_end); let left_shift_part = op_flag.left_shift() * op_flag.overflow(); let right_shift_part = op_flag.right_shift(); - let call_part = call_or_syscall * (depth_next - E::from(16u32)); + let call_part = call_or_dyncall_or_syscall * (depth_next - E::from(16u32)); // Enforces constraints of the transition of depth of the stack. result[0] = no_shift_part + left_shift_part - right_shift_part + call_part; diff --git a/air/src/trace/decoder/mod.rs b/air/src/trace/decoder/mod.rs index 77a7531d7f..aaad96cc42 100644 --- a/air/src/trace/decoder/mod.rs +++ b/air/src/trace/decoder/mod.rs @@ -83,7 +83,7 @@ pub const IS_LOOP_BODY_FLAG_COL_IDX: usize = HASHER_STATE_RANGE.start + 4; /// Index of a flag column which indicates whether an ending block is a LOOP block. pub const IS_LOOP_FLAG_COL_IDX: usize = HASHER_STATE_RANGE.start + 5; -/// Index of a flag column which indicates whether an ending block is a CALL block. +/// Index of a flag column which indicates whether an ending block is a CALL or DYNCALL block. pub const IS_CALL_FLAG_COL_IDX: usize = HASHER_STATE_RANGE.start + 6; /// Index of a flag column which indicates whether an ending block is a SYSCALL block. diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index 5abffe7327..eebc8e2779 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -137,7 +137,7 @@ impl MainTrace { /// Returns a specific element from the hasher state at row i. pub fn decoder_hasher_state_element(&self, element: usize, i: RowIndex) -> Felt { - self.columns.get_column(DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET + element)[i + 1] + self.columns.get_column(DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET + element)[i] } /// Returns the current function hash (i.e., root) at row i. @@ -240,6 +240,8 @@ impl MainTrace { ([e0, b3, b2, b1] == [ONE, ZERO, ONE, ZERO]) || // REPEAT ([b6, b5, b4, b3, b2, b1, b0] == [ONE, ONE, ONE, ZERO, ONE, ZERO, ZERO]) || + // DYN + ([b6, b5, b4, b3, b2, b1, b0] == [ONE, ZERO, ONE, ONE, ZERO, ZERO, ZERO]) || // END of a loop ([b6, b5, b4, b3, b2, b1, b0] == [ONE, ONE, ONE, ZERO, ZERO, ZERO, ZERO] && h5 == ONE) } diff --git a/assembly/src/assembler/instruction/procedures.rs b/assembly/src/assembler/instruction/procedures.rs index 8316422095..0c7b2600c0 100644 --- a/assembly/src/assembler/instruction/procedures.rs +++ b/assembly/src/assembler/instruction/procedures.rs @@ -42,15 +42,12 @@ impl Assembler { Ok(Some(dyn_node_id)) } - /// Creates a new CALL block whose target is DYN. + /// Creates a new DYNCALL block for the dynamic function call and return. pub(super) fn dyncall( &self, mast_forest_builder: &mut MastForestBuilder, ) -> Result, AssemblyError> { - let dyn_call_node_id = { - let dyn_node_id = mast_forest_builder.ensure_dyn()?; - mast_forest_builder.ensure_call(dyn_node_id)? - }; + let dyn_call_node_id = mast_forest_builder.ensure_dyncall()?; Ok(Some(dyn_call_node_id)) } diff --git a/assembly/src/assembler/mast_forest_builder.rs b/assembly/src/assembler/mast_forest_builder.rs index dc320c9ca0..0d99ab55de 100644 --- a/assembly/src/assembler/mast_forest_builder.rs +++ b/assembly/src/assembler/mast_forest_builder.rs @@ -425,6 +425,11 @@ impl MastForestBuilder { self.ensure_node(MastNode::new_dyn()) } + /// Adds a dyncall node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_dyncall(&mut self) -> Result { + self.ensure_node(MastNode::new_dyncall()) + } + /// Adds an external node to the forest, and returns the [`MastNodeId`] associated with it. pub fn ensure_external(&mut self, mast_root: RpoDigest) -> Result { self.ensure_node(MastNode::new_external(mast_root)) diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index d361e1c60d..fb168e8c2e 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -1898,7 +1898,7 @@ fn program_with_dynamic_code_execution_in_new_context() -> TestResult { let program = context.assemble(source)?; let expected = "\ begin - call.0xc75c340ec6a69e708457544d38783abbb604d881b7dc62d00bfc2b10f52808e6 + dyncall end"; assert_str_eq!(format!("{program}"), expected); Ok(()) diff --git a/core/src/mast/mod.rs b/core/src/mast/mod.rs index bc9cc8e3b5..980949e34b 100644 --- a/core/src/mast/mod.rs +++ b/core/src/mast/mod.rs @@ -138,6 +138,11 @@ impl MastForest { self.add_node(MastNode::new_dyn()) } + /// Adds a dyncall node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_dyncall(&mut self) -> Result { + self.add_node(MastNode::new_dyncall()) + } + /// Adds an external node to the forest, and returns the [`MastNodeId`] associated with it. pub fn add_external(&mut self, mast_root: RpoDigest) -> Result { self.add_node(MastNode::new_external(mast_root)) diff --git a/core/src/mast/node/dyn_node.rs b/core/src/mast/node/dyn_node.rs index 91afeea798..8bdaf516a7 100644 --- a/core/src/mast/node/dyn_node.rs +++ b/core/src/mast/node/dyn_node.rs @@ -6,15 +6,16 @@ use miden_formatting::prettier::{const_text, nl, Document, PrettyPrint}; use crate::{ mast::{DecoratorId, MastForest}, - OPCODE_DYN, + OPCODE_DYN, OPCODE_DYNCALL, }; // DYN NODE // ================================================================================================ /// A Dyn node specifies that the node to be executed next is defined dynamically via the stack. -#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct DynNode { + is_dyncall: bool, before_enter: Vec, after_exit: Vec, } @@ -22,28 +23,73 @@ pub struct DynNode { /// Constants impl DynNode { /// The domain of the Dyn block (used for control block hashing). - pub const DOMAIN: Felt = Felt::new(OPCODE_DYN as u64); + pub const DYN_DOMAIN: Felt = Felt::new(OPCODE_DYN as u64); + + /// The domain of the Dyncall block (used for control block hashing). + pub const DYNCALL_DOMAIN: Felt = Felt::new(OPCODE_DYNCALL as u64); } /// Public accessors impl DynNode { + /// Creates a new [`DynNode`] representing a dynexec operation. + pub fn new_dyn() -> Self { + Self { + is_dyncall: false, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } + + /// Creates a new [`DynNode`] representing a dyncall operation. + pub fn new_dyncall() -> Self { + Self { + is_dyncall: true, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } + + /// Returns true if the [`DynNode`] represents a dyncall operation, and false for dynexec. + pub fn is_dyncall(&self) -> bool { + self.is_dyncall + } + + /// Returns the domain of this dyn node. + pub fn domain(&self) -> Felt { + if self.is_dyncall() { + Self::DYNCALL_DOMAIN + } else { + Self::DYN_DOMAIN + } + } + /// Returns a commitment to a Dyn node. /// /// The commitment is computed by hashing two empty words ([ZERO; 4]) in the domain defined - /// by [Self::DOMAIN], i.e.: + /// by [Self::DYN_DOMAIN] or [Self::DYNCALL_DOMAIN], i.e.: /// /// ``` /// # use miden_core::mast::DynNode; /// # use miden_crypto::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}}; - /// Hasher::merge_in_domain(&[Digest::default(), Digest::default()], DynNode::DOMAIN); + /// Hasher::merge_in_domain(&[Digest::default(), Digest::default()], DynNode::DYN_DOMAIN); + /// Hasher::merge_in_domain(&[Digest::default(), Digest::default()], DynNode::DYNCALL_DOMAIN); /// ``` pub fn digest(&self) -> RpoDigest { - RpoDigest::new([ - Felt::new(8115106948140260551), - Felt::new(13491227816952616836), - Felt::new(15015806788322198710), - Felt::new(16575543461540527115), - ]) + if self.is_dyncall { + RpoDigest::new([ + Felt::new(8751004906421739448), + Felt::new(13469709002495534233), + Felt::new(12584249374630430826), + Felt::new(7624899870831503004), + ]) + } else { + RpoDigest::new([ + Felt::new(8115106948140260551), + Felt::new(13491227816952616836), + Felt::new(15015806788322198710), + Felt::new(16575543461540527115), + ]) + } } /// Returns the decorators to be executed before this node is executed. @@ -132,7 +178,11 @@ impl DynNodePrettyPrint<'_> { impl crate::prettier::PrettyPrint for DynNodePrettyPrint<'_> { fn render(&self) -> crate::prettier::Document { - let dyn_text = const_text("dyn"); + let dyn_text = if self.node.is_dyncall() { + const_text("dyncall") + } else { + const_text("dyn") + }; let single_line = self.single_line_pre_decorators() + dyn_text.clone() @@ -164,8 +214,19 @@ mod tests { #[test] pub fn test_dyn_node_digest() { assert_eq!( - DynNode::default().digest(), - Rpo256::merge_in_domain(&[RpoDigest::default(), RpoDigest::default()], DynNode::DOMAIN) + DynNode::new_dyn().digest(), + Rpo256::merge_in_domain( + &[RpoDigest::default(), RpoDigest::default()], + DynNode::DYN_DOMAIN + ) + ); + + assert_eq!( + DynNode::new_dyncall().digest(), + Rpo256::merge_in_domain( + &[RpoDigest::default(), RpoDigest::default()], + DynNode::DYNCALL_DOMAIN + ) ); } } diff --git a/core/src/mast/node/mod.rs b/core/src/mast/node/mod.rs index 2163565742..2dfa9dd037 100644 --- a/core/src/mast/node/mod.rs +++ b/core/src/mast/node/mod.rs @@ -95,7 +95,10 @@ impl MastNode { } pub fn new_dyn() -> Self { - Self::Dyn(DynNode::default()) + Self::Dyn(DynNode::new_dyn()) + } + pub fn new_dyncall() -> Self { + Self::Dyn(DynNode::new_dyncall()) } pub fn new_external(mast_root: RpoDigest) -> Self { @@ -173,7 +176,7 @@ impl MastNode { MastNode::Split(_) => SplitNode::DOMAIN, MastNode::Loop(_) => LoopNode::DOMAIN, MastNode::Call(call_node) => call_node.domain(), - MastNode::Dyn(_) => DynNode::DOMAIN, + MastNode::Dyn(dyn_node) => dyn_node.domain(), MastNode::External(_) => panic!("Can't fetch domain for an `External` node."), } } diff --git a/core/src/mast/serialization/info.rs b/core/src/mast/serialization/info.rs index 4a3fa58652..13aec7780e 100644 --- a/core/src/mast/serialization/info.rs +++ b/core/src/mast/serialization/info.rs @@ -86,6 +86,7 @@ impl MastNodeInfo { Ok(MastNode::Call(syscall)) }, MastNodeType::Dyn => Ok(MastNode::new_dyn()), + MastNodeType::Dyncall => Ok(MastNode::new_dyncall()), MastNodeType::External => Ok(MastNode::new_external(self.digest)), } } @@ -119,7 +120,8 @@ const BLOCK: u8 = 3; const CALL: u8 = 4; const SYSCALL: u8 = 5; const DYN: u8 = 6; -const EXTERNAL: u8 = 7; +const DYNCALL: u8 = 7; +const EXTERNAL: u8 = 8; /// Represents the variant of a [`MastNode`], as well as any additional data. For example, for more /// efficient decoding, and because of the frequency with which these node types appear, we directly @@ -154,6 +156,7 @@ pub enum MastNodeType { callee_id: u32, } = SYSCALL, Dyn = DYN, + Dyncall = DYNCALL, External = EXTERNAL, } @@ -188,7 +191,13 @@ impl MastNodeType { Self::Call { callee_id: call_node.callee().0 } } }, - Dyn(_) => Self::Dyn, + Dyn(dyn_node) => { + if dyn_node.is_dyncall() { + Self::Dyncall + } else { + Self::Dyn + } + }, External(_) => Self::External, } } @@ -215,6 +224,7 @@ impl Serializable for MastNodeType { MastNodeType::Call { callee_id } => Self::encode_u32_payload(callee_id), MastNodeType::SysCall { callee_id } => Self::encode_u32_payload(callee_id), MastNodeType::Dyn => 0, + MastNodeType::Dyncall => 0, MastNodeType::External => 0, }; @@ -297,6 +307,7 @@ impl Deserializable for MastNodeType { Ok(Self::SysCall { callee_id }) }, DYN => Ok(Self::Dyn), + DYNCALL => Ok(Self::Dyncall), EXTERNAL => Ok(Self::External), _ => Err(DeserializationError::InvalidValue(format!( "Invalid tag for MAST node: {discriminant}" diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index c76a3ff3ac..e0716d5bb1 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -27,6 +27,7 @@ fn confirm_operation_and_decorator_structure() { Operation::Loop => (), Operation::Call => (), Operation::Dyn => (), + Operation::Dyncall => (), Operation::SysCall => (), Operation::Span => (), Operation::End => (), @@ -329,6 +330,11 @@ fn serialize_deserialize_all_nodes() { mast_forest[dyn_node_id].set_before_enter(vec![decorator_id1]); mast_forest[dyn_node_id].set_after_exit(vec![decorator_id2]); + // Dyncall node + let dyncall_node_id = mast_forest.add_dyncall().unwrap(); + mast_forest[dyncall_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[dyncall_node_id].set_after_exit(vec![decorator_id2]); + // External node let external_node_id = mast_forest.add_external(RpoDigest::default()).unwrap(); mast_forest[external_node_id].set_before_enter(vec![decorator_id1]); @@ -339,6 +345,7 @@ fn serialize_deserialize_all_nodes() { mast_forest.make_root(loop_node_id); mast_forest.make_root(split_node_id); mast_forest.make_root(dyn_node_id); + mast_forest.make_root(dyncall_node_id); mast_forest.make_root(external_node_id); let serialized_mast_forest = mast_forest.to_bytes(); diff --git a/core/src/mast/tests.rs b/core/src/mast/tests.rs index aa224e9296..31da93ba7b 100644 --- a/core/src/mast/tests.rs +++ b/core/src/mast/tests.rs @@ -10,8 +10,8 @@ use crate::{chiplets::hasher, mast::DynNode, Kernel, ProgramInfo, Word}; #[test] fn dyn_hash_is_correct() { let expected_constant = - hasher::merge_in_domain(&[RpoDigest::default(), RpoDigest::default()], DynNode::DOMAIN); - assert_eq!(expected_constant, DynNode::default().digest()); + hasher::merge_in_domain(&[RpoDigest::default(), RpoDigest::default()], DynNode::DYN_DOMAIN); + assert_eq!(expected_constant, DynNode::new_dyn().digest()); } proptest! { diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index 1bb1c7afad..7a2eb6e3c0 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -111,6 +111,7 @@ pub(super) mod opcode_constants { pub const OPCODE_RCOMBBASE: u8 = 0b0101_1001; pub const OPCODE_EMIT: u8 = 0b0101_1010; pub const OPCODE_PUSH: u8 = 0b0101_1011; + pub const OPCODE_DYNCALL: u8 = 0b0101_1100; pub const OPCODE_MRUPDATE: u8 = 0b0110_0000; /* unused: 0b0110_0100 */ @@ -184,6 +185,9 @@ pub enum Operation { /// Marks the beginning of a dynamic code block, where the target is specified by the stack. Dyn = OPCODE_DYN, + /// Marks the beginning of a dynamic function call, where the target is specified by the stack. + Dyncall = OPCODE_DYNCALL, + /// Marks the beginning of a kernel call. SysCall = OPCODE_SYSCALL, @@ -589,8 +593,10 @@ impl Operation { } } - /// Returns true if this operation is a control operation. - pub fn is_control_op(&self) -> bool { + /// Returns true if this operation writes any data to the decoder hasher registers. + /// + /// In other words, if so, then the user op helper registers are not available. + pub fn populates_decoder_hasher_registers(&self) -> bool { matches!( self, Self::End @@ -603,7 +609,6 @@ impl Operation { | Self::Halt | Self::Call | Self::SysCall - | Self::Dyn ) } } @@ -634,6 +639,7 @@ impl fmt::Display for Operation { Self::Split => write!(f, "split"), Self::Loop => write!(f, "loop"), Self::Call => writeln!(f, "call"), + Self::Dyncall => writeln!(f, "dyncall"), Self::SysCall => writeln!(f, "syscall"), Self::Dyn => writeln!(f, "dyn"), Self::Span => write!(f, "span"), @@ -771,6 +777,7 @@ impl Serializable for Operation { | Operation::Loop | Operation::Call | Operation::Dyn + | Operation::Dyncall | Operation::SysCall | Operation::Span | Operation::End @@ -950,6 +957,7 @@ impl Deserializable for Operation { OPCODE_SPAN => Self::Span, OPCODE_JOIN => Self::Join, OPCODE_DYN => Self::Dyn, + OPCODE_DYNCALL => Self::Dyncall, OPCODE_RCOMBBASE => Self::RCombBase, OPCODE_MRUPDATE => Self::MrUpdate, diff --git a/miden/tests/integration/flow_control/mod.rs b/miden/tests/integration/flow_control/mod.rs index eec7f0ea99..e00d370c33 100644 --- a/miden/tests/integration/flow_control/mod.rs +++ b/miden/tests/integration/flow_control/mod.rs @@ -230,62 +230,51 @@ fn simple_syscall() { fn simple_dyn_exec() { let program_source = " proc.foo - # drop the top 4 values, since that will be the code hash when we call this dynamically - dropw add end begin - # call foo directly so it will get added to the CodeBlockTable - padw + # call foo directly call.foo # move the first result of foo out of the way movdn.4 - # use dynexec to call foo again via its hash, which is on the stack + # use dynexec to call foo again via its hash, which is stored at memory location 42 + mem_storew.42 dropw + push.42 dynexec end"; - // The hash of foo can be obtained from the code block table by: - // let program = test.compile(); - // let cb_table = program.cb_table(); - // Result: - // [BaseElement(14592192105906586403), BaseElement(9256464248508904838), - // BaseElement(17436090329036592832), BaseElement(10814467189528518943)] - // Integer values can be obtained via Felt::from_mont(14592192105906586403).as_int(), etc. + // The hash of foo can be obtained with: + // let context = assembly::testing::TestContext::new(); + // let program = context.assemble(program_source).unwrap(); + // let procedure_digests: Vec = program.mast_forest().procedure_digests().collect(); + // let foo_digest = procedure_digests[0]; + // std::println!("foo digest: {foo_digest:?}"); + // As ints: - // [16045159387802755434, 10308872899350860082, 17306481765929021384, 16642043361554117790] + // [7259075614730273379, 2498922176515930900, 11574583201486131710, 6285975441353882141] + + let stack_init: [u64; 7] = [ + 3, + // put the hash of foo on the stack + 7259075614730273379, + 2498922176515930900, + 11574583201486131710, + 6285975441353882141, + 1, + 2, + ]; let test = Test { - stack_inputs: StackInputs::try_from_ints([ - 3, - // put the hash of foo on the stack - 16045159387802755434, - 10308872899350860082, - 17306481765929021384, - 16642043361554117790, - 1, - 2, - ]) - .unwrap(), - ..Test::new(&format!("test{}", line!()), program_source, false) + stack_inputs: StackInputs::try_from_ints(stack_init).unwrap(), + ..Test::new(&format!("test{}", line!()), program_source, true) }; test.expect_stack(&[6]); - test.prove_and_verify( - vec![ - 3, - 16045159387802755434, - 10308872899350860082, - 17306481765929021384, - 16642043361554117790, - 1, - 2, - ], - false, - ); + test.prove_and_verify(stack_init.to_vec(), false); } #[test] @@ -294,16 +283,15 @@ fn dynexec_with_procref() { use.external::module proc.foo - dropw push.1.2 u32wrapping_add end begin - procref.foo + procref.foo mem_storew.42 dropw push.42 dynexec - procref.module::func + procref.module::func mem_storew.42 dropw push.42 dynexec dup @@ -319,7 +307,6 @@ fn dynexec_with_procref() { "external::module".parse().unwrap(), "\ export.func - dropw u32wrapping_add.1 end ", @@ -332,9 +319,6 @@ fn dynexec_with_procref() { fn simple_dyncall() { let program_source = " proc.foo - # drop the top 4 values, since that will be the code hash when we call this dynamically - dropw - # test that the execution context has changed mem_load.0 assertz @@ -346,37 +330,39 @@ fn simple_dyncall() { # write to memory so we can test that `call` and `dyncall` change the execution context push.5 mem_store.0 - # call foo directly so it will get added to the CodeBlockTable - padw + # call foo directly call.foo # move the first result of foo out of the way movdn.4 # use dyncall to call foo again via its hash, which is on the stack + mem_storew.42 dropw + push.42 dyncall swapw dropw end"; // The hash of foo can be obtained with: - // let context = TestContext::new(); + // let context = assembly::testing::TestContext::new(); // let program = context.assemble(program_source).unwrap(); // let procedure_digests: Vec = program.mast_forest().procedure_digests().collect(); // let foo_digest = procedure_digests[0]; + // std::println!("foo digest: {foo_digest:?}"); + // - // Integer values can be obtained via Felt::from_mont(14592192105906586403).as_int(), etc. // As ints: - // [8324248212344458853, 17691992706129158519, 18131640149172243086, 16129275750103409835] + // [6751154577850596602, 235765701633049111, 16334162752640292120, 7786442719091086500] let test = Test { stack_inputs: StackInputs::try_from_ints([ 3, // put the hash of foo on the stack - 8324248212344458853, - 17691992706129158519, - 18131640149172243086, - 16129275750103409835, + 6751154577850596602, + 235765701633049111, + 16334162752640292120, + 7786442719091086500, 1, 2, ]) @@ -390,10 +376,10 @@ fn simple_dyncall() { test.prove_and_verify( vec![ 3, - 8324248212344458853, - 17691992706129158519, - 18131640149172243086, - 16129275750103409835, + 6751154577850596602, + 235765701633049111, + 16334162752640292120, + 7786442719091086500, 1, 2, ], @@ -401,6 +387,55 @@ fn simple_dyncall() { ); } +/// Calls `bar` dynamically, which issues a syscall. We ensure that the `caller` instruction in the +/// kernel procedure correctly returns the hash of `bar`. +/// +/// We also populate the stack before `dyncall` to ensure that stack depth is properly restored +/// after `dyncall`. +#[test] +fn dyncall_with_syscall_and_caller() { + let kernel_source = " + export.foo + caller + end + "; + + let program_source = " + proc.bar + syscall.foo + end + + begin + # Populate stack before call + push.1 push.2 push.3 push.4 padw + + # Prepare dyncall + procref.bar mem_storew.42 dropw push.42 + dyncall + + # Truncate stack + movupw.3 dropw movupw.3 dropw + end"; + + let mut test = Test::new(&format!("test{}", line!()), program_source, true); + test.kernel_source = Some( + test.source_manager + .load(&format!("kernel{}", line!()), kernel_source.to_string()), + ); + test.expect_stack(&[ + 7618101086444903432, + 9972424747203251625, + 14917526361757867843, + 9845116178182948544, + 4, + 3, + 2, + 1, + ]); + + test.prove_and_verify(vec![], false); +} + // PROCREF INSTRUCTION // ================================================================================================ diff --git a/processor/src/chiplets/aux_trace/mod.rs b/processor/src/chiplets/aux_trace/mod.rs index 7e33a942c7..9ae777d2ef 100644 --- a/processor/src/chiplets/aux_trace/mod.rs +++ b/processor/src/chiplets/aux_trace/mod.rs @@ -17,10 +17,10 @@ use miden_air::{ RowIndex, }; use vm_core::{ - Word, ONE, OPCODE_CALL, OPCODE_DYN, OPCODE_END, OPCODE_HPERM, OPCODE_JOIN, OPCODE_LOOP, - OPCODE_MLOAD, OPCODE_MLOADW, OPCODE_MPVERIFY, OPCODE_MRUPDATE, OPCODE_MSTORE, OPCODE_MSTOREW, - OPCODE_MSTREAM, OPCODE_PIPE, OPCODE_RCOMBBASE, OPCODE_RESPAN, OPCODE_SPAN, OPCODE_SPLIT, - OPCODE_SYSCALL, OPCODE_U32AND, OPCODE_U32XOR, ZERO, + Word, ONE, OPCODE_CALL, OPCODE_DYN, OPCODE_DYNCALL, OPCODE_END, OPCODE_HPERM, OPCODE_JOIN, + OPCODE_LOOP, OPCODE_MLOAD, OPCODE_MLOADW, OPCODE_MPVERIFY, OPCODE_MRUPDATE, OPCODE_MSTORE, + OPCODE_MSTOREW, OPCODE_MSTREAM, OPCODE_PIPE, OPCODE_RCOMBBASE, OPCODE_RESPAN, OPCODE_SPAN, + OPCODE_SPLIT, OPCODE_SYSCALL, OPCODE_U32AND, OPCODE_U32XOR, ZERO, }; use super::{super::trace::AuxColumnBuilder, Felt, FieldElement}; @@ -241,8 +241,15 @@ impl> AuxColumnBuilder for BusColumnBuilder let op_code = op_code_felt.as_int() as u8; match op_code { - OPCODE_JOIN | OPCODE_SPLIT | OPCODE_LOOP | OPCODE_DYN | OPCODE_CALL => { - build_control_block_request(main_trace, op_code_felt, alphas, row) + OPCODE_JOIN | OPCODE_SPLIT | OPCODE_LOOP | OPCODE_CALL => build_control_block_request( + main_trace, + main_trace.decoder_hasher_state(row), + op_code_felt, + alphas, + row, + ), + OPCODE_DYN | OPCODE_DYNCALL => { + build_dyn_block_request(main_trace, op_code_felt, alphas, row) }, OPCODE_SYSCALL => build_syscall_block_request(main_trace, op_code_felt, alphas, row), OPCODE_SPAN => build_span_block_request(main_trace, alphas, row), @@ -289,6 +296,7 @@ impl> AuxColumnBuilder for BusColumnBuilder /// Builds requests made to the hasher chiplet at the start of a control block. fn build_control_block_request>( main_trace: &MainTrace, + decoder_hasher_state: [Felt; 8], op_code_felt: Felt, alphas: &[E], row: RowIndex, @@ -300,9 +308,27 @@ fn build_control_block_request>( let header = alphas[0] + alphas[1].mul_base(Felt::from(transition_label)) + alphas[2].mul_base(addr_nxt); - let state = main_trace.decoder_hasher_state(row); + header + build_value(&alphas[8..16], &decoder_hasher_state) + alphas[5].mul_base(op_code_felt) +} + +/// Builds requests made on a `DYN` or `DYNCALL` operation. +fn build_dyn_block_request>( + main_trace: &MainTrace, + op_code_felt: Felt, + alphas: &[E], + row: RowIndex, +) -> E { + let control_block_req = + build_control_block_request(main_trace, [ZERO; 8], op_code_felt, alphas, row); + + let memory_req = { + let mem_addr = main_trace.stack_element(0, row); + let mem_value = main_trace.decoder_hasher_state_first_half(row); + + compute_memory_request(main_trace, MEMORY_READ_LABEL, alphas, row, mem_addr, mem_value) + }; - header + build_value(&alphas[8..16], &state) + alphas[5].mul_base(op_code_felt) + control_block_req * memory_req } /// Builds requests made to kernel ROM chiplet when initializing a syscall block. @@ -312,7 +338,13 @@ fn build_syscall_block_request>( alphas: &[E], row: RowIndex, ) -> E { - let factor1 = build_control_block_request(main_trace, op_code_felt, alphas, row); + let factor1 = build_control_block_request( + main_trace, + main_trace.decoder_hasher_state(row), + op_code_felt, + alphas, + row, + ); let op_label = KERNEL_PROC_LABEL; let state = main_trace.decoder_hasher_state(row); diff --git a/processor/src/decoder/aux_trace/block_hash_table.rs b/processor/src/decoder/aux_trace/block_hash_table.rs index 818df3512f..75d831b1fb 100644 --- a/processor/src/decoder/aux_trace/block_hash_table.rs +++ b/processor/src/decoder/aux_trace/block_hash_table.rs @@ -1,7 +1,7 @@ use miden_air::RowIndex; use vm_core::{ - Word, OPCODE_CALL, OPCODE_DYN, OPCODE_END, OPCODE_HALT, OPCODE_JOIN, OPCODE_LOOP, - OPCODE_REPEAT, OPCODE_SPLIT, OPCODE_SYSCALL, ZERO, + Word, OPCODE_CALL, OPCODE_DYN, OPCODE_DYNCALL, OPCODE_END, OPCODE_HALT, OPCODE_JOIN, + OPCODE_LOOP, OPCODE_REPEAT, OPCODE_SPLIT, OPCODE_SYSCALL, ZERO, }; use super::{AuxColumnBuilder, Felt, FieldElement, MainTrace, ONE}; @@ -55,9 +55,8 @@ impl> AuxColumnBuilder for BlockHashTableCo .map(|row| row.collapse(alphas)) .unwrap_or(E::ONE), OPCODE_REPEAT => BlockHashTableRow::from_repeat(main_trace, row).collapse(alphas), - OPCODE_DYN => BlockHashTableRow::from_dyn(main_trace, row).collapse(alphas), - OPCODE_CALL | OPCODE_SYSCALL => { - BlockHashTableRow::from_call_or_syscall(main_trace, row).collapse(alphas) + OPCODE_DYN | OPCODE_DYNCALL | OPCODE_CALL | OPCODE_SYSCALL => { + BlockHashTableRow::from_dyn_dyncall_call_syscall(main_trace, row).collapse(alphas) }, _ => E::ONE, } @@ -209,27 +208,12 @@ impl BlockHashTableRow { } } - /// Computes the row to add to the block hash table when encountering a `DYN` operation. - pub fn from_dyn(main_trace: &MainTrace, row: RowIndex) -> Self { - let child_block_hash = { - // Note: the child block hash is found on the stack, and hence in reverse order. - let s0 = main_trace.stack_element(0, row); - let s1 = main_trace.stack_element(1, row); - let s2 = main_trace.stack_element(2, row); - let s3 = main_trace.stack_element(3, row); - - [s3, s2, s1, s0] - }; - - Self { - parent_block_id: main_trace.addr(row + 1), - child_block_hash, - is_first_child: false, - is_loop_body: false, - } - } - - pub fn from_call_or_syscall(main_trace: &MainTrace, row: RowIndex) -> Self { + /// Computes the row to add to the block hash table when encountering a `DYN`, `DYNCALL`, `CALL` + /// or `SYSCALL` operation. + /// + /// The hash of the child node being called is expected to be in the first half of the decoder + /// hasher state. + pub fn from_dyn_dyncall_call_syscall(main_trace: &MainTrace, row: RowIndex) -> Self { Self { parent_block_id: main_trace.addr(row + 1), child_block_hash: main_trace.decoder_hasher_state_first_half(row), diff --git a/processor/src/decoder/aux_trace/block_stack_table.rs b/processor/src/decoder/aux_trace/block_stack_table.rs index e3e5260184..691d71a43b 100644 --- a/processor/src/decoder/aux_trace/block_stack_table.rs +++ b/processor/src/decoder/aux_trace/block_stack_table.rs @@ -1,7 +1,7 @@ use miden_air::RowIndex; use vm_core::{ - OPCODE_CALL, OPCODE_DYN, OPCODE_END, OPCODE_JOIN, OPCODE_LOOP, OPCODE_RESPAN, OPCODE_SPAN, - OPCODE_SPLIT, OPCODE_SYSCALL, + OPCODE_CALL, OPCODE_DYN, OPCODE_DYNCALL, OPCODE_END, OPCODE_JOIN, OPCODE_LOOP, OPCODE_RESPAN, + OPCODE_SPAN, OPCODE_SPLIT, OPCODE_SYSCALL, }; use super::{AuxColumnBuilder, Felt, FieldElement, MainTrace, ONE, ZERO}; @@ -35,8 +35,8 @@ impl> AuxColumnBuilder for BlockStackColumn let op_code = op_code_felt.as_int() as u8; match op_code { - OPCODE_JOIN | OPCODE_SPLIT | OPCODE_SPAN | OPCODE_DYN | OPCODE_LOOP | OPCODE_RESPAN - | OPCODE_CALL | OPCODE_SYSCALL => { + OPCODE_JOIN | OPCODE_SPLIT | OPCODE_SPAN | OPCODE_DYN | OPCODE_DYNCALL + | OPCODE_LOOP | OPCODE_RESPAN | OPCODE_CALL | OPCODE_SYSCALL => { get_block_stack_table_inclusion_multiplicand(main_trace, i, alphas, op_code) }, _ => E::ONE, @@ -137,6 +137,29 @@ fn get_block_stack_table_inclusion_multiplicand, ) -> Felt { - // make sure execution context was provided for CALL and SYSCALL blocks - if block_type == BlockType::Call || block_type == BlockType::SysCall { + // make sure execution context was provided for CALL, SYSCALL and DYNCALL blocks + if block_type == BlockType::Call + || block_type == BlockType::SysCall + || block_type == BlockType::Dyncall + { debug_assert!(ctx_info.is_some(), "no execution context provided for a CALL block"); } else { debug_assert!(ctx_info.is_none(), "execution context provided for a non-CALL block"); @@ -127,10 +130,10 @@ impl BlockInfo { } } - /// Returns ONE if this block is a CALL block; otherwise returns ZERO. + /// Returns ONE if this block is a CALL or DYNCALL block; otherwise returns ZERO. pub const fn is_call(&self) -> Felt { match self.block_type { - BlockType::Call => ONE, + BlockType::Call | BlockType::Dyncall => ONE, _ => ZERO, } } @@ -194,6 +197,7 @@ pub enum BlockType { Loop(bool), // internal value set to false if the loop is never entered Call, Dyn, + Dyncall, SysCall, Span, } diff --git a/processor/src/decoder/mod.rs b/processor/src/decoder/mod.rs index 92bb25e4a5..a6248d0efb 100644 --- a/processor/src/decoder/mod.rs +++ b/processor/src/decoder/mod.rs @@ -249,7 +249,7 @@ where self.system.start_syscall(); self.decoder.start_syscall(callee_hash, addr, ctx_info); } else { - self.system.start_call(callee_hash); + self.system.start_call_or_dyncall(callee_hash); self.decoder.start_call(callee_hash, addr, ctx_info); } @@ -292,23 +292,119 @@ where // -------------------------------------------------------------------------------------------- /// Starts decoding of a DYN node. - pub(super) fn start_dyn_node(&mut self) -> Result<(), ExecutionError> { + /// + /// Note: even though we will write the callee hash to h[0..4] for the chiplets bus and block + /// hash table, the issued hash request is still hash([ZERO; 8]). + pub(super) fn start_dyn_node(&mut self, dyn_node: &DynNode) -> Result { + debug_assert!(!dyn_node.is_dyncall()); + + let mem_addr = self.stack.get(0); + // The callee hash is stored in memory, and the address is specified on the top of the + // stack. + let callee_hash = self.read_mem_word(mem_addr)?; + let addr = self.chiplets.hash_control_block( EMPTY_WORD, EMPTY_WORD, - DynNode::DOMAIN, - DynNode::default().digest(), + dyn_node.domain(), + dyn_node.digest(), ); - self.decoder.start_dyn(addr); - self.execute_op(Operation::Noop) + self.decoder.start_dyn(addr, callee_hash); + + // Pop the memory address off the stack. + self.execute_op(Operation::Drop)?; + + Ok(callee_hash) + } + + /// Starts decoding of a DYNCALL node. + /// + /// Note: even though we will write the callee hash to h[0..4] for the chiplets bus and block + /// hash table, and the stack helper registers to h[4..5], the issued hash request is still + /// hash([ZERO; 8]). + pub(super) fn start_dyncall_node( + &mut self, + dyn_node: &DynNode, + ) -> Result { + debug_assert!(dyn_node.is_dyncall()); + + let mem_addr = self.stack.get(0); + // The callee hash is stored in memory, and the address is specified on the top of the + // stack. + let callee_hash = self.read_mem_word(mem_addr)?; + + // Note: other functions end in "executing a Noop", which + // 1. ensures trace capacity, + // 2. copies the stack over to the next row, + // 3. advances clock. + // + // Dyncall's effect on the trace can't be written in terms of any other operation, and + // therefore can't follow this framework. Hence, we do it "manually". It's probably worth + // refactoring the decoder though to remove this Noop execution pattern. + self.ensure_trace_capacity(); + + let addr = self.chiplets.hash_control_block( + EMPTY_WORD, + EMPTY_WORD, + dyn_node.domain(), + dyn_node.digest(), + ); + + let (stack_depth, next_overflow_addr) = self.stack.shift_left_and_start_context(); + debug_assert!(stack_depth <= u32::MAX as usize, "stack depth too big"); + + let ctx_info = ExecutionContextInfo::new( + self.system.ctx(), + self.system.fn_hash(), + self.system.fmp(), + stack_depth as u32, + next_overflow_addr, + ); + + self.system.start_call_or_dyncall(callee_hash); + self.decoder.start_dyncall(addr, callee_hash, ctx_info); + + self.advance_clock()?; + + Ok(callee_hash) } /// Ends decoding of a DYN node. - pub(super) fn end_dyn_node(&mut self) -> Result<(), ExecutionError> { + pub(super) fn end_dyn_node(&mut self, dyn_node: &DynNode) -> 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(DynNode::default().digest().into()); + self.decoder.end_control_block(dyn_node.digest().into()); + + self.execute_op(Operation::Noop) + } + + /// Ends decoding of a DYNCALL node. + pub(super) fn end_dyncall_node(&mut self, dyn_node: &DynNode) -> Result<(), ExecutionError> { + // when a DYNCALL block ends, stack depth must be exactly 16 + let stack_depth = self.stack.depth(); + if stack_depth > MIN_STACK_DEPTH { + return Err(ExecutionError::InvalidStackDepthOnReturn(stack_depth)); + } + + // 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 + let ctx_info = self + .decoder + .end_control_block(dyn_node.digest().into()) + .expect("no execution context"); + + // when returning from a function call, restore the context of the system + // registers and the operand stack to what it was prior to the call. + self.system.restore_context( + ctx_info.parent_ctx, + ctx_info.parent_fmp, + ctx_info.parent_fn_hash, + ); + self.stack.restore_context( + ctx_info.parent_stack_depth as usize, + ctx_info.parent_next_overflow_addr, + ); self.execute_op(Operation::Noop) } @@ -532,16 +628,50 @@ impl Decoder { /// Starts decoding of a DYN block. /// + /// Note that even though the hasher decoder columns are populated, the issued hash request is + /// still for [ZERO; 8 | domain=DYN]. This is because a `DYN` node takes its child on the stack, + /// and therefore the child hash cannot be included in the `DYN` node hash computation (see + /// [`vm_core::mast::DynNode`]). The decoder hasher columns are then not needed for the `DYN` + /// node hash computation, and so were used to store the result of the memory read operation for + /// the child hash. + /// /// 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, addr: Felt) { + pub fn start_dyn(&mut self, addr: Felt, callee_hash: Word) { // 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, [ZERO; 4], [ZERO; 4]); + self.trace + .append_block_start(parent_addr, Operation::Dyn, callee_hash, [ZERO; 4]); self.debug_info.append_operation(Operation::Dyn); } + /// Starts decoding of a DYNCALL block. + /// + /// Note that even though the hasher decoder columns are populated, the issued hash request is + /// still for [ZERO; 8 | domain=DYNCALL]. + /// + /// This pushes a block with ID=addr onto the block stack and appends execution of a DYNCALL + /// operation to the trace. The decoder hasher trace columns are populated with the callee hash, + /// as well as the stack helper registers (specifically their state after shifting the stack + /// left). We need to store those in the decoder trace so that the block stack table can access + /// them (since in the next row, we start a new context, and hence the stack registers are reset + /// to their default values). + pub fn start_dyncall(&mut self, addr: Felt, callee_hash: Word, ctx_info: ExecutionContextInfo) { + let parent_stack_depth = ctx_info.parent_stack_depth.into(); + let parent_next_overflow_addr = ctx_info.parent_next_overflow_addr; + + let parent_addr = self.block_stack.push(addr, BlockType::Dyncall, Some(ctx_info)); + self.trace.append_block_start( + parent_addr, + Operation::Dyncall, + callee_hash, + [parent_stack_depth, parent_next_overflow_addr, ZERO, ZERO], + ); + + self.debug_info.append_operation(Operation::Dyncall); + } + /// 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 @@ -655,7 +785,10 @@ impl Decoder { /// TODO: it might be better to get the operation information from the decoder trace, rather /// than passing it in as a parameter. pub fn set_user_op_helpers(&mut self, op: Operation, values: &[Felt]) { - debug_assert!(!op.is_control_op(), "op is a control operation"); + debug_assert!( + !op.populates_decoder_hasher_registers(), + "user op helper registers not available for op" + ); self.trace.set_user_op_helpers(values); } diff --git a/processor/src/decoder/tests.rs b/processor/src/decoder/tests.rs index b298864598..35208c7ab6 100644 --- a/processor/src/decoder/tests.rs +++ b/processor/src/decoder/tests.rs @@ -1282,8 +1282,20 @@ fn syscall_block() { // ================================================================================================ #[test] fn dyn_block() { - // build a dynamic block which looks like this: - // push.1 add + // Equivalent masm: + // + // proc.foo + // push.1 add + // end + // + // begin + // # stack: [42, DIGEST] + // mstorew + // push.42 + // dynexec + // end + + const FOO_ROOT_NODE_ADDR: u64 = 42; let mut mast_forest = MastForest::new(); @@ -1292,13 +1304,17 @@ fn dyn_block() { let foo_root_node_id = mast_forest.add_node(foo_root_node.clone()).unwrap(); mast_forest.make_root(foo_root_node_id); - let mul_bb_node = MastNode::new_basic_block(vec![Operation::Mul], None).unwrap(); - let mul_bb_node_id = mast_forest.add_node(mul_bb_node.clone()).unwrap(); + let mstorew_node = MastNode::new_basic_block(vec![Operation::MStoreW], None).unwrap(); + let mstorew_node_id = mast_forest.add_node(mstorew_node.clone()).unwrap(); - let save_bb_node = MastNode::new_basic_block(vec![Operation::MovDn4], None).unwrap(); - let save_bb_node_id = mast_forest.add_node(save_bb_node.clone()).unwrap(); + let push_node = MastNode::new_basic_block( + vec![Operation::Push(Felt::from(FOO_ROOT_NODE_ADDR as u32))], + None, + ) + .unwrap(); + let push_node_id = mast_forest.add_node(push_node.clone()).unwrap(); - let join_node = MastNode::new_join(mul_bb_node_id, save_bb_node_id, &mast_forest).unwrap(); + let join_node = MastNode::new_join(mstorew_node_id, push_node_id, &mast_forest).unwrap(); let join_node_id = mast_forest.add_node(join_node.clone()).unwrap(); // This dyn will point to foo. @@ -1317,8 +1333,7 @@ fn dyn_block() { foo_root_node.digest()[1].as_int(), foo_root_node.digest()[2].as_int(), foo_root_node.digest()[3].as_int(), - 2, - 4, + FOO_ROOT_NODE_ADDR, ], &program, ); @@ -1329,30 +1344,39 @@ fn dyn_block() { let join_addr = INIT_ADDR + EIGHT; check_op_decoding(&trace, 1, INIT_ADDR, Operation::Join, 0, 0, 0); // starting first span - let mul_basic_block_addr = join_addr + EIGHT; + let mstorew_basic_block_addr = join_addr + EIGHT; check_op_decoding(&trace, 2, join_addr, Operation::Span, 1, 0, 0); - check_op_decoding(&trace, 3, mul_basic_block_addr, Operation::Mul, 0, 0, 1); - check_op_decoding(&trace, 4, mul_basic_block_addr, Operation::End, 0, 0, 0); + check_op_decoding(&trace, 3, mstorew_basic_block_addr, Operation::MStoreW, 0, 0, 1); + check_op_decoding(&trace, 4, mstorew_basic_block_addr, Operation::End, 0, 0, 0); // starting second span - let save_basic_block_addr = mul_basic_block_addr + EIGHT; - check_op_decoding(&trace, 5, join_addr, Operation::Span, 1, 0, 0); - check_op_decoding(&trace, 6, save_basic_block_addr, Operation::MovDn4, 0, 0, 1); - check_op_decoding(&trace, 7, save_basic_block_addr, Operation::End, 0, 0, 0); + let push_basic_block_addr = mstorew_basic_block_addr + EIGHT; + check_op_decoding(&trace, 5, join_addr, Operation::Span, 2, 0, 0); + check_op_decoding( + &trace, + 6, + push_basic_block_addr, + Operation::Push(Felt::from(FOO_ROOT_NODE_ADDR as u32)), + 1, + 0, + 1, + ); + check_op_decoding(&trace, 7, push_basic_block_addr, Operation::Noop, 0, 1, 1); + check_op_decoding(&trace, 8, push_basic_block_addr, Operation::End, 0, 0, 0); // end inner join - check_op_decoding(&trace, 8, join_addr, Operation::End, 0, 0, 0); + check_op_decoding(&trace, 9, join_addr, Operation::End, 0, 0, 0); // dyn - check_op_decoding(&trace, 9, INIT_ADDR, Operation::Dyn, 0, 0, 0); + check_op_decoding(&trace, 10, INIT_ADDR, Operation::Dyn, 0, 0, 0); // starting foo span - let dyn_addr = save_basic_block_addr + EIGHT; + let dyn_addr = push_basic_block_addr + EIGHT; let add_basic_block_addr = dyn_addr + EIGHT; - check_op_decoding(&trace, 10, dyn_addr, Operation::Span, 2, 0, 0); - check_op_decoding(&trace, 11, add_basic_block_addr, Operation::Push(ONE), 1, 0, 1); - check_op_decoding(&trace, 12, add_basic_block_addr, Operation::Add, 0, 1, 1); - check_op_decoding(&trace, 13, add_basic_block_addr, Operation::End, 0, 0, 0); + check_op_decoding(&trace, 11, dyn_addr, Operation::Span, 2, 0, 0); + check_op_decoding(&trace, 12, add_basic_block_addr, Operation::Push(ONE), 1, 0, 1); + check_op_decoding(&trace, 13, add_basic_block_addr, Operation::Add, 0, 1, 1); + check_op_decoding(&trace, 14, add_basic_block_addr, Operation::End, 0, 0, 0); // end dyn - check_op_decoding(&trace, 14, dyn_addr, Operation::End, 0, 0, 0); + check_op_decoding(&trace, 15, dyn_addr, Operation::End, 0, 0, 0); // end outer join - check_op_decoding(&trace, 15, INIT_ADDR, Operation::End, 0, 0, 0); + check_op_decoding(&trace, 16, INIT_ADDR, Operation::End, 0, 0, 0); // --- check hasher state columns ------------------------------------------------------------- @@ -1363,8 +1387,8 @@ fn dyn_block() { assert_eq!(dyn_hash, get_hasher_state2(&trace, 0)); // in the second row, the hasher set is set to hashes of both child nodes of the inner JOIN - let mul_bb_node_hash: Word = mul_bb_node.digest().into(); - let save_bb_node_hash: Word = save_bb_node.digest().into(); + let mul_bb_node_hash: Word = mstorew_node.digest().into(); + let save_bb_node_hash: Word = push_node.digest().into(); assert_eq!(mul_bb_node_hash, get_hasher_state1(&trace, 1)); assert_eq!(save_bb_node_hash, get_hasher_state2(&trace, 1)); @@ -1373,32 +1397,31 @@ fn dyn_block() { assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 4)); // at the end of the second SPAN, the hasher state is set to the hash of the second child - assert_eq!(save_bb_node_hash, get_hasher_state1(&trace, 7)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 7)); + assert_eq!(save_bb_node_hash, get_hasher_state1(&trace, 8)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 8)); // at the end of the inner JOIN, the hasher set is set to the hash of the JOIN - assert_eq!(join_hash, get_hasher_state1(&trace, 8)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 8)); + assert_eq!(join_hash, get_hasher_state1(&trace, 9)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 9)); - // at the start of the DYN block, the hasher state is set to ZERO + // at the start of the DYN block, the hasher state is set to foo digest let foo_hash: Word = foo_root_node.digest().into(); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state1(&trace, 9)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 9)); + assert_eq!(foo_hash, get_hasher_state1(&trace, 10)); // at the end of the DYN SPAN, the hasher state is set to the hash of the foo span - assert_eq!(foo_hash, get_hasher_state1(&trace, 13)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 13)); + assert_eq!(foo_hash, get_hasher_state1(&trace, 14)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 14)); // at the end of the DYN block, the hasher state is set to the hash of the DYN node - assert_eq!(dyn_hash, get_hasher_state1(&trace, 14)); + assert_eq!(dyn_hash, get_hasher_state1(&trace, 15)); // at the end of the program, the hasher state is set to the hash of the entire program let program_hash: Word = program_root_node.digest().into(); - assert_eq!(program_hash, get_hasher_state1(&trace, 15)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 15)); + assert_eq!(program_hash, get_hasher_state1(&trace, 16)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&trace, 16)); // the HALT opcode and program hash get propagated to the last row - for i in 16..trace_len { + for i in 17..trace_len { assert!(contains_op(&trace, i, Operation::Halt)); assert_eq!(ZERO, trace[OP_BITS_EXTRA_COLS_RANGE.start][i]); assert_eq!(ONE, trace[OP_BITS_EXTRA_COLS_RANGE.start + 1][i]); diff --git a/processor/src/decoder/trace.rs b/processor/src/decoder/trace.rs index 6842c504e3..47cf9ba15c 100644 --- a/processor/src/decoder/trace.rs +++ b/processor/src/decoder/trace.rs @@ -85,16 +85,18 @@ impl DecoderTrace { // -------------------------------------------------------------------------------------------- /// Appends a trace row marking the start of a flow control block (JOIN, SPLIT, LOOP, CALL, - /// SYSCALL). + /// SYSCALL, DYN, DYNCALL). /// /// When a control block is starting, we do the following: /// - Set the address to the address of the parent block. This is not necessarily equal to the /// address from the previous row because in a SPLIT block, the second child follows the first /// child, rather than the parent. - /// - Set op_bits to opcode of the specified block (e.g., JOIN, SPLIT, LOOP, CALL, SYSCALL). + /// - Set op_bits to opcode of the specified block (e.g., JOIN, SPLIT, LOOP, CALL, SYSCALL, DYN, + /// DYNCALL). /// - Set the first half of the hasher state to the h1 parameter. For JOIN and SPLIT blocks this /// will contain the hash of the left child; for LOOP block this will contain hash of the - /// loop's body, for CALL and SYSCALL block this will contain hash of the called function. + /// loop's body, for CALL, SYSCALL, DYN and DYNCALL blocks this will contain hash of the + /// called function. /// - Set the second half of the hasher state to the h2 parameter. For JOIN and SPLIT blocks /// this will contain hash of the right child. /// - Set is_span to ZERO. diff --git a/processor/src/lib.rs b/processor/src/lib.rs index da55082a1d..037f9269b9 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -24,7 +24,9 @@ pub use vm_core::{ StackInputs, StackOutputs, Word, EMPTY_WORD, ONE, ZERO, }; use vm_core::{ - mast::{BasicBlockNode, CallNode, JoinNode, LoopNode, OpBatch, SplitNode, OP_GROUP_SIZE}, + mast::{ + BasicBlockNode, CallNode, DynNode, JoinNode, LoopNode, OpBatch, SplitNode, OP_GROUP_SIZE, + }, Decorator, DecoratorIterator, FieldElement, }; pub use winter_prover::matrix::ColMatrix; @@ -278,7 +280,7 @@ where MastNode::Split(node) => self.execute_split_node(node, program)?, MastNode::Loop(node) => self.execute_loop_node(node, program)?, MastNode::Call(node) => self.execute_call_node(node, program)?, - MastNode::Dyn(_) => self.execute_dyn_node(program)?, + MastNode::Dyn(node) => self.execute_dyn_node(node, program)?, MastNode::External(external_node) => { let node_digest = external_node.digest(); let mast_forest = self @@ -408,11 +410,16 @@ where /// The MAST root of the callee is assumed to be at the top of the stack, and the callee is /// expected to be either in the current `program` or in the host. #[inline(always)] - fn execute_dyn_node(&mut self, program: &MastForest) -> Result<(), ExecutionError> { - self.start_dyn_node()?; - - // get target hash from the stack - let callee_hash = self.stack.get_word(0); + fn execute_dyn_node( + &mut self, + node: &DynNode, + program: &MastForest, + ) -> Result<(), ExecutionError> { + let callee_hash = if node.is_dyncall() { + self.start_dyncall_node(node)? + } else { + self.start_dyn_node(node)? + }; // if the callee is not in the program's MAST forest, try to find a MAST forest for it in // the host (corresponding to an external library loaded in the host); if none are @@ -436,7 +443,11 @@ where }, } - self.end_dyn_node() + if node.is_dyncall() { + self.end_dyncall_node(node) + } else { + self.end_dyn_node(node) + } } /// Executes the specified [BasicBlockNode]. diff --git a/processor/src/operations/io_ops.rs b/processor/src/operations/io_ops.rs index 067f26cb51..d6765bb2ac 100644 --- a/processor/src/operations/io_ops.rs +++ b/processor/src/operations/io_ops.rs @@ -35,12 +35,11 @@ where /// Thus, the net result of the operation is that the stack is shifted left by one item. pub(super) fn op_mloadw(&mut self) -> Result<(), ExecutionError> { // get the address from the stack and read the word from current memory context - let ctx = self.system.ctx(); - let addr = Self::get_valid_address(self.stack.get(0))?; - let word = self.chiplets.read_mem(ctx, addr); + let mut word = self.read_mem_word(self.stack.get(0))?; + word.reverse(); - // reverse the order of the memory word & update the stack state - for (i, &value) in word.iter().rev().enumerate() { + // update the stack state + for (i, &value) in word.iter().enumerate() { self.stack.set(i, value); } self.stack.shift_left(5); @@ -62,10 +61,7 @@ where /// register 0. pub(super) fn op_mload(&mut self) -> Result<(), ExecutionError> { // get the address from the stack and read the word from memory - let ctx = self.system.ctx(); - let addr = Self::get_valid_address(self.stack.get(0))?; - let mut word = self.chiplets.read_mem(ctx, addr); - // put the retrieved word into stack order + let mut word = self.read_mem_word(self.stack.get(0))?; word.reverse(); // update the stack state @@ -252,6 +248,15 @@ where // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- + /// Returns the memory word at address `addr` in the current context. + pub(crate) fn read_mem_word(&mut self, addr: Felt) -> Result { + let ctx = self.system.ctx(); + let mem_addr = Self::get_valid_address(addr)?; + let word_at_addr = self.chiplets.read_mem(ctx, mem_addr); + + Ok(word_at_addr) + } + /// Checks that provided address is less than u32::MAX and returns it cast to u32. /// /// # Errors diff --git a/processor/src/operations/mod.rs b/processor/src/operations/mod.rs index a28c12d651..58216b7e00 100644 --- a/processor/src/operations/mod.rs +++ b/processor/src/operations/mod.rs @@ -51,6 +51,7 @@ where Operation::Call => unreachable!("control flow operation"), Operation::SysCall => unreachable!("control flow operation"), Operation::Dyn => unreachable!("control flow operation"), + Operation::Dyncall => unreachable!("control flow operation"), Operation::Span => unreachable!("control flow operation"), Operation::Repeat => unreachable!("control flow operation"), Operation::Respan => unreachable!("control flow operation"), @@ -160,7 +161,7 @@ where } /// Increments the clock cycle for all components of the process. - fn advance_clock(&mut self) -> Result<(), ExecutionError> { + pub(super) fn advance_clock(&mut self) -> Result<(), ExecutionError> { self.system.advance_clock(self.max_cycles)?; self.stack.advance_clock(); self.chiplets.advance_clock(); @@ -168,7 +169,7 @@ where } /// Makes sure there is enough memory allocated for the trace to accommodate a new clock cycle. - fn ensure_trace_capacity(&mut self) { + pub(super) fn ensure_trace_capacity(&mut self) { self.system.ensure_trace_capacity(); self.stack.ensure_trace_capacity(); } diff --git a/processor/src/stack/aux_trace.rs b/processor/src/stack/aux_trace.rs index 426239dbe2..8c34985756 100644 --- a/processor/src/stack/aux_trace.rs +++ b/processor/src/stack/aux_trace.rs @@ -1,6 +1,7 @@ use alloc::vec::Vec; use miden_air::{trace::main_trace::MainTrace, RowIndex}; +use vm_core::OPCODE_DYNCALL; use super::{Felt, FieldElement, OverflowTableRow}; use crate::trace::AuxColumnBuilder; @@ -21,6 +22,8 @@ impl AuxTraceBuilder { rand_elements: &[E], ) -> Vec> { let p1 = self.build_aux_column(main_trace, rand_elements); + + debug_assert_eq!(*p1.last().unwrap(), E::ONE); vec![p1] } } @@ -29,6 +32,7 @@ impl> AuxColumnBuilder for AuxTraceBuilder /// Removes a row from the stack overflow table. fn get_requests_at(&self, main_trace: &MainTrace, alphas: &[E], i: RowIndex) -> E { let is_left_shift = main_trace.is_left_shift(i); + let is_dyncall = main_trace.get_op_code(i) == OPCODE_DYNCALL.into(); let is_non_empty_overflow = main_trace.is_non_empty_overflow(i); if is_left_shift && is_non_empty_overflow { @@ -36,8 +40,13 @@ impl> AuxColumnBuilder for AuxTraceBuilder let s15_prime = main_trace.stack_element(15, i + 1); let b1_prime = main_trace.parent_overflow_address(i + 1); - let row = OverflowTableRow::new(b1, s15_prime, b1_prime); - row.to_value(alphas) + OverflowTableRow::new(b1, s15_prime, b1_prime).to_value(alphas) + } else if is_dyncall && is_non_empty_overflow { + let b1 = main_trace.parent_overflow_address(i); + let s15_prime = main_trace.stack_element(15, i + 1); + let b1_prime = main_trace.decoder_hasher_state_element(5, i); + + OverflowTableRow::new(b1, s15_prime, b1_prime).to_value(alphas) } else { E::ONE } diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index 417c0a80af..6708840b02 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -199,16 +199,42 @@ impl Stack { debug_assert!(start_pos > 0, "start position must be greater than 0"); debug_assert!(start_pos <= MIN_STACK_DEPTH, "start position cannot exceed stack top size"); + let (next_depth, next_overflow_addr) = self.shift_left_no_helpers(start_pos); + self.trace.set_helpers_at(self.clk.as_usize(), next_depth, next_overflow_addr); + } + + /// Copies stack values starting at the specified position at the current clock cycle to + /// position + 1 at the next clock cycle + /// + /// If stack depth grows beyond 16 items, the additional item is pushed into the overflow table. + pub fn shift_right(&mut self, start_pos: usize) { + debug_assert!(start_pos < MIN_STACK_DEPTH, "start position cannot exceed stack top size"); + + // Update the stack. + self.trace.stack_shift_right_at(self.clk, start_pos); + + // Update the overflow table. + let to_overflow = self.trace.get_stack_value_at(self.clk, MAX_TOP_IDX); + self.overflow.push(to_overflow, Felt::from(self.clk)); + + // Stack depth always increases on right shift. + self.active_depth += 1; + self.full_depth += 1; + } + + /// Shifts the stack left, and returns the value for the helper columns B0 and B1, without + /// writing them to the trace. + fn shift_left_no_helpers(&mut self, start_pos: usize) -> (Felt, Felt) { match self.active_depth { 0..=MAX_TOP_IDX => unreachable!("stack underflow"), MIN_STACK_DEPTH => { // Shift in a ZERO, to prevent depth shrinking below the minimum stack depth. - self.trace.stack_shift_left_at(self.clk, start_pos, ZERO, None); + self.trace.stack_shift_left_no_helpers(self.clk, start_pos, ZERO, None) }, _ => { // Update the stack & overflow table. let from_overflow = self.overflow.pop(u64::from(self.clk)); - self.trace.stack_shift_left_at( + let helpers = self.trace.stack_shift_left_no_helpers( self.clk, start_pos, from_overflow, @@ -218,32 +244,43 @@ impl Stack { // Stack depth only decreases when it is greater than the minimum stack depth. self.active_depth -= 1; self.full_depth -= 1; + + helpers }, } } - /// Copies stack values starting at the specified position at the current clock cycle to - /// position + 1 at the next clock cycle - /// - /// If stack depth grows beyond 16 items, the additional item is pushed into the overflow table. - pub fn shift_right(&mut self, start_pos: usize) { - debug_assert!(start_pos < MIN_STACK_DEPTH, "start position cannot exceed stack top size"); - - // Update the stack. - self.trace.stack_shift_right_at(self.clk, start_pos); + // CONTEXT MANAGEMENT + // -------------------------------------------------------------------------------------------- - // Update the overflow table. - let to_overflow = self.trace.get_stack_value_at(self.clk, MAX_TOP_IDX); - self.overflow.push(to_overflow, Felt::from(self.clk)); + /// Shifts the stack left, writes the default values for the stack helper registers in the trace + /// (stack depth and next overflow address), and returns the value of those helper registers + /// before the new context wipe. + /// + /// This specialized method is needed because the other ones write the updated helper register + /// values directly to the trace in the next row. However, the dyncall instruction needs to + /// shift the stack left, and start a new context simultaneously (and hence reset the stack + /// helper registers to their default value). It is assumed that the caller will write the + /// return values somewhere else in the trace. + pub fn shift_left_and_start_context(&mut self) -> (usize, Felt) { + const START_POSITION: usize = 1; + + self.shift_left_no_helpers(START_POSITION); + + // reset the helper columns to their default value, and write those to the trace in the next + // row. + let (next_depth, next_overflow_addr) = self.start_context(); + // Note: `start_context()` reset `active_depth` to 16, and `overflow.last_row_addr` to 0. + self.trace.set_helpers_at( + self.clk.as_usize(), + Felt::from(self.active_depth as u32), + self.overflow.last_row_addr(), + ); - // Stack depth always increases on right shift. - self.active_depth += 1; - self.full_depth += 1; + // return the helper registers' state before the new context + (next_depth, next_overflow_addr) } - // CONTEXT MANAGEMENT - // -------------------------------------------------------------------------------------------- - /// Starts a new execution context for this stack and returns a tuple consisting of the current /// stack depth and the address of the overflow table row prior to starting the new context. /// diff --git a/processor/src/stack/trace.rs b/processor/src/stack/trace.rs index 2aeb49b8f3..f1dcc72101 100644 --- a/processor/src/stack/trace.rs +++ b/processor/src/stack/trace.rs @@ -87,25 +87,24 @@ impl StackTrace { } /// Copies the stack values starting at the specified position at the specified clock cycle to - /// position - 1 at the next clock cycle. + /// position - 1 at the next clock cycle. Returns the new value of the helper registers without + /// writing them to the next row (i.e. the stack depth and the next overflow addr). /// /// The final stack item column is filled with the provided value in `last_value`. /// /// If next_overflow_addr is provided, this function assumes that the stack depth has been /// decreased by one and a row has been removed from the overflow table. Thus, it makes the - /// following changes to the helper columns: + /// following changes to the helper columns (without writing them to the next row): /// - Decrement the stack depth (b0) by one. /// - Sets b1 to the address of the top row in the overflow table to the specified /// `next_overflow_addr`. - /// - Set h0 to (depth - 16). Inverses of these values will be computed in into_array() method - /// after the entire trace is constructed. - pub fn stack_shift_left_at( + pub(super) fn stack_shift_left_no_helpers( &mut self, clk: RowIndex, start_pos: usize, last_value: Felt, next_overflow_addr: Option, - ) { + ) -> (Felt, Felt) { let clk = clk.as_usize(); // update stack top columns @@ -114,15 +113,15 @@ impl StackTrace { } self.stack[MAX_TOP_IDX][clk + 1] = last_value; - // update stack helper columns + // return stack helper columns if let Some(next_overflow_addr) = next_overflow_addr { let next_depth = self.helpers[0][clk] - ONE; - self.set_helpers_at(clk, next_depth, next_overflow_addr); + (next_depth, next_overflow_addr) } else { - // if next_overflow_addr was not provide, just copy over the values from the last row + // if next_overflow_addr was not provide, just return the values from the last row let next_depth = self.helpers[0][clk]; let next_overflow_addr = self.helpers[1][clk]; - self.set_helpers_at(clk, next_depth, next_overflow_addr); + (next_depth, next_overflow_addr) } } @@ -193,7 +192,12 @@ impl StackTrace { /// set to (stack_depth - 16) rather than to 1 / (stack_depth - 16). Inverses of these values /// will be computed in into_array() method (using batch inversion) after the entire trace is /// constructed. - fn set_helpers_at(&mut self, clk: usize, stack_depth: Felt, next_overflow_addr: Felt) { + pub(super) fn set_helpers_at( + &mut self, + clk: usize, + stack_depth: Felt, + next_overflow_addr: Felt, + ) { self.helpers[0][clk + 1] = stack_depth; self.helpers[1][clk + 1] = next_overflow_addr; self.helpers[2][clk + 1] = stack_depth - Felt::from(MIN_STACK_DEPTH as u32); diff --git a/processor/src/system/mod.rs b/processor/src/system/mod.rs index f6b50d0eaa..016560b544 100644 --- a/processor/src/system/mod.rs +++ b/processor/src/system/mod.rs @@ -178,8 +178,8 @@ impl System { /// - Sets the free memory pointer to its initial value (FMP_MIN). /// - Sets the hash of the function which initiated the current context to the provided value. /// - /// A CALL cannot be started when the VM is executing a SYSCALL. - pub fn start_call(&mut self, fn_hash: Word) { + /// A CALL or DYNCALL cannot be started when the VM is executing a SYSCALL. + pub fn start_call_or_dyncall(&mut self, fn_hash: Word) { debug_assert!(!self.in_syscall, "call in syscall"); self.ctx = (self.clk + 1).into(); self.fmp = Felt::new(FMP_MIN);