From 76af51592da8af0f533508f3fd526b009e15d6e6 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 20 Sep 2022 18:06:40 -0700 Subject: [PATCH] feat: add execution context to block stack table --- miden/tests/integration/flow_control/mod.rs | 33 ++++- processor/src/decoder/aux_hints.rs | 41 +++++- processor/src/decoder/block_stack.rs | 138 +++++++++++--------- processor/src/decoder/mod.rs | 63 ++++++--- processor/src/decoder/tests.rs | 101 +++++++------- processor/src/errors.rs | 5 +- 6 files changed, 240 insertions(+), 141 deletions(-) diff --git a/miden/tests/integration/flow_control/mod.rs b/miden/tests/integration/flow_control/mod.rs index 8f95d24f35..d507712538 100644 --- a/miden/tests/integration/flow_control/mod.rs +++ b/miden/tests/integration/flow_control/mod.rs @@ -1,4 +1,4 @@ -use crate::build_test; +use crate::{build_test, helpers::TestError}; // SIMPLE FLOW CONTROL TESTS // ================================================================================================ @@ -123,19 +123,42 @@ fn if_in_loop_in_if() { #[test] fn local_fn_call() { + // returning from a function with non-empty overflow table should result in an error let source = " proc.foo - add + push.1 end begin call.foo end"; - let test = build_test!(source, &[1, 2]); - test.expect_stack(&[3]); + let expected_err = TestError::ExecutionError("InvalidStackDepthOnReturn(17)"); + build_test!(source, &[1, 2]).expect_error(expected_err); + + // dropping values from the stack in the current execution context should not affect values + // in the overflow table from the parent execution context + let source = " + proc.foo + repeat.20 + drop + end + end + + begin + push.18 + call.foo + repeat.16 + drop + end + end"; + + let inputs = (1_u64..18).collect::>(); + + let test = build_test!(source, &inputs); + test.expect_stack(&[2, 1]); - test.prove_and_verify(vec![1, 2], false); + test.prove_and_verify(inputs, false); } #[test] diff --git a/processor/src/decoder/aux_hints.rs b/processor/src/decoder/aux_hints.rs index a8973e5885..dc1c7c3600 100644 --- a/processor/src/decoder/aux_hints.rs +++ b/processor/src/decoder/aux_hints.rs @@ -276,21 +276,30 @@ pub enum OpGroupTableUpdate { // ================================================================================================ /// Describes a single entry in the block stack table. An entry in the block stack table is a tuple -/// (block_id, parent_id, is_loop). +/// (block_id, parent_id, is_loop, parent_ctx, fmp, stack_depth, next_overflow_addr). #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct BlockStackTableRow { block_id: Felt, parent_id: Felt, is_loop: bool, + parent_ctx: u32, + fmp: Felt, + stack_depth: u32, + next_overflow_addr: Felt, } impl BlockStackTableRow { /// Returns a new [BlockStackTableRow] instantiated from the specified block info. pub fn new(block_info: &BlockInfo) -> Self { + let ctx_info = block_info.ctx_info.unwrap_or_default(); Self { block_id: block_info.addr, parent_id: block_info.parent_addr, is_loop: block_info.is_entered_loop() == ONE, + parent_ctx: ctx_info.parent_ctx, + fmp: ctx_info.fmp, + stack_depth: ctx_info.stack_depth, + next_overflow_addr: ctx_info.next_overflow_addr, } } @@ -302,19 +311,47 @@ impl BlockStackTableRow { block_id, parent_id, is_loop, + parent_ctx: 0, + fmp: ZERO, + stack_depth: 0, + next_overflow_addr: ZERO, + } + } + + #[cfg(test)] + /// Returns a new [BlockStackTableRow] corresponding to a CALL code block. This is used for + /// test purpose only. + pub fn new_test_with_ctx( + block_id: Felt, + parent_id: Felt, + is_loop: bool, + ctx_info: super::ExecutionContextInfo, + ) -> Self { + Self { + block_id, + parent_id, + is_loop, + parent_ctx: ctx_info.parent_ctx, + fmp: ctx_info.fmp, + stack_depth: ctx_info.stack_depth, + next_overflow_addr: ctx_info.next_overflow_addr, } } } impl LookupTableRow for BlockStackTableRow { /// Reduces this row to a single field element in the field specified by E. This requires - /// at least 4 alpha values. + /// at least 8 alpha values. fn to_value>(&self, alphas: &[E]) -> E { let is_loop = if self.is_loop { ONE } else { ZERO }; alphas[0] + alphas[1].mul_base(self.block_id) + alphas[2].mul_base(self.parent_id) + alphas[3].mul_base(is_loop) + + alphas[4].mul_base(Felt::from(self.parent_ctx)) + + alphas[5].mul_base(self.fmp) + + alphas[6].mul_base(Felt::from(self.stack_depth)) + + alphas[7].mul_base(self.next_overflow_addr) } } diff --git a/processor/src/decoder/block_stack.rs b/processor/src/decoder/block_stack.rs index b16011d23c..159e923982 100644 --- a/processor/src/decoder/block_stack.rs +++ b/processor/src/decoder/block_stack.rs @@ -14,44 +14,55 @@ impl BlockStack { // -------------------------------------------------------------------------------------------- /// Pushes a new code block onto the block stack and returns the address of the block's parent. - /// block_type can be anything except for CALL block. /// /// The block is identified by its address, and we also need to know what type of a block this - /// is. Other information (i.e., the block's parent, whether the block is a body of - /// a loop or a first child of a JOIN block) is determined from the information already on the - /// stack. - /// - /// For non-CALL blocks, we set parent context and free memory pointer to zero values. - /// - /// # Panics - /// Panics if the block type is a CALL block. - pub fn push(&mut self, addr: Felt, block_type: BlockType) -> Felt { - assert_ne!(block_type, BlockType::Call, "cannot be a call block"); - let (parent_addr, is_loop_body, is_first_child) = self.get_new_block_context(); - self.blocks.push(BlockInfo { - addr, - block_type, - parent_addr, - parent_ctx: 0, - parent_fmp: ZERO, - is_loop_body, - is_first_child, - }); - parent_addr - } + /// is. Additionally, for CALL blocks, execution context info must be provided. Other + /// information (i.e., the block's parent, whether the block is a body of a loop or a first + /// child of a JOIN block) is determined from the information already on the stack. + pub fn push( + &mut self, + addr: Felt, + block_type: BlockType, + ctx_info: Option, + ) -> Felt { + // make sure execution context was provided when expected + if block_type == BlockType::Call { + 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" + ); + } + + // determine additional info about the new block based on its parent + let (parent_addr, is_loop_body, is_first_child) = match self.blocks.last() { + Some(parent) => match parent.block_type { + // if the parent block is a LOOP block, the new block must be a loop body + BlockType::Loop(loop_entered) => { + debug_assert!(loop_entered, "parent is un-entered loop"); + (parent.addr, true, false) + } + // if the parent block is a JOIN block, figure out if the new block is the first + // or the second child + BlockType::Join(first_child_executed) => { + (parent.addr, false, !first_child_executed) + } + _ => (parent.addr, false, false), + }, + // if the block stack is empty, a new block is neither a body of a loop nor the first + // child of a JOIN block; also, we set the parent address to ZERO. + None => (ZERO, false, false), + }; - /// Pushes a new CALL block onto the block stack and returns the address of the block's parent. - /// - /// This is similar to pushing any other block onto the stack but unlike with all other blocks, - /// we initialize parent context and free memory pointer with the specified values. - pub fn push_call(&mut self, addr: Felt, parent_ctx: u32, parent_fmp: Felt) -> Felt { - let (parent_addr, is_loop_body, is_first_child) = self.get_new_block_context(); self.blocks.push(BlockInfo { addr, - block_type: BlockType::Call, + block_type, parent_addr, - parent_ctx, - parent_fmp, + ctx_info, is_loop_body, is_first_child, }); @@ -82,37 +93,6 @@ impl BlockStack { pub fn peek_mut(&mut self) -> &mut BlockInfo { self.blocks.last_mut().expect("block stack is empty") } - - // HELPER METHODS - // -------------------------------------------------------------------------------------------- - - /// Returns context information for a block which is about to be pushed onto the block stack. - /// - /// Context information consists of a tuple (parent_addr, is_loop_body, is_first_child), where: - /// - parent_addr is the address of the block currently at the top of the stack. - /// - is_loop_body is set to true if the newly added block will be a body of a loop. - /// - is_first_child is set to true if the newly added block will be a first child in the - /// JOIN block. - fn get_new_block_context(&self) -> (Felt, bool, bool) { - match self.blocks.last() { - Some(parent) => match parent.block_type { - // if the current block is a LOOP block, the new block must be a loop body - BlockType::Loop(loop_entered) => { - debug_assert!(loop_entered, "parent is un-entered loop"); - (parent.addr, true, false) - } - // if the current block is a JOIN block, figure out if the new block is the first - // or the second child - BlockType::Join(first_child_executed) => { - (parent.addr, false, !first_child_executed) - } - _ => (parent.addr, false, false), - }, - // if the block stack is empty, a new block is neither a body of a loop nor the first - // child of a JOIN block; also, we set the parent address to ZERO. - None => (ZERO, false, false), - } - } } // BLOCK INFO @@ -124,8 +104,7 @@ pub struct BlockInfo { pub addr: Felt, block_type: BlockType, pub parent_addr: Felt, - pub parent_ctx: u32, - pub parent_fmp: Felt, + pub ctx_info: Option, pub is_loop_body: bool, pub is_first_child: bool, } @@ -177,6 +156,35 @@ impl BlockInfo { } } +// BLOCK CONTEXT INFO +// ================================================================================================ + +/// Contains information about an execution context. Execution context are relevant only for CALL +/// blocks. +#[derive(Debug, Default, Clone, Copy)] +pub struct ExecutionContextInfo { + /// Context ID of the block's parent. + pub parent_ctx: u32, + /// Value of free memory pointer right before a CALL instruction is executed. + pub fmp: Felt, + /// Depth of the operand stack right before a CALL operation is executed. + pub stack_depth: u32, + /// Address of the top row in the overflow table right before a CALL operations is executed. + pub next_overflow_addr: Felt, +} + +impl ExecutionContextInfo { + /// Returns an new [ExecutionContextInfo] instantiated with the specified parameters. + pub fn new(parent_ctx: u32, fmp: Felt, stack_depth: u32, next_overflow_addr: Felt) -> Self { + Self { + parent_ctx, + fmp, + stack_depth, + next_overflow_addr, + } + } +} + // BLOCK TYPE // ================================================================================================ diff --git a/processor/src/decoder/mod.rs b/processor/src/decoder/mod.rs index 43e4300f10..eebbb224e5 100644 --- a/processor/src/decoder/mod.rs +++ b/processor/src/decoder/mod.rs @@ -9,6 +9,7 @@ use vm_core::{ NUM_HASHER_COLUMNS, NUM_OP_BATCH_FLAGS, NUM_OP_BITS, OP_BATCH_1_GROUPS, OP_BATCH_2_GROUPS, OP_BATCH_4_GROUPS, OP_BATCH_8_GROUPS, }, + stack::STACK_TOP_SIZE, AssemblyOp, }; @@ -16,7 +17,7 @@ mod trace; use trace::DecoderTrace; mod block_stack; -use block_stack::{BlockInfo, BlockStack, BlockType}; +use block_stack::{BlockInfo, BlockStack, BlockType, ExecutionContextInfo}; mod aux_hints; pub use aux_hints::{ @@ -173,12 +174,21 @@ impl Process { .chiplets .hash_control_block(fn_hash, [ZERO; 4], block.hash()); + // start new execution context in for the operand stack. this has the effect of + // resetting stack depth to 16. + let (stack_depth, next_overflow_addr) = self.stack.start_context(); + debug_assert!(stack_depth <= u32::MAX as usize, "stack depth too big"); + // start decoding the CALL block; this appends a row with CALL operation to the decoder - // trace and records the state of execution context and free memory pointer in the block - // stack table. these will be used to restore ctx/fmp after the function returns. - let ctx = self.system.ctx(); - let fmp = self.system.fmp(); - self.decoder.start_call(fn_hash, addr, ctx, fmp); + // trace and records information about the current execution context in the block stack + // table. this info will be used to restore the context after the function returns. + let ctx_info = ExecutionContextInfo::new( + self.system.ctx(), + self.system.fmp(), + stack_depth as u32, + next_overflow_addr, + ); + self.decoder.start_call(fn_hash, addr, ctx_info); // set the execution context to the current clock cycle + 1. This ensures that the context // is globally unique as is never set to 0. also, reset the free memory pointer to min @@ -192,17 +202,28 @@ impl Process { /// Ends decoding of a CALL block. pub(super) fn end_call_block(&mut self, block: &Call) -> Result<(), ExecutionError> { - // this appends a row with END operation to the decoder trace; the returned values contain - // execution context and free memory pointer prior to executing the CALL block - let (ctx, fmp) = self.decoder.end_control_block(block.hash().into()); + // when a CALL block ends, stack depth must be exactly 16 + let stack_depth = self.stack.depth(); + if stack_depth > STACK_TOP_SIZE { + return Err(ExecutionError::InvalidStackDepthOnReturn(stack_depth)); + } + + // this appends a row with END operation to the decoder trace; the returned value contains + // information about the execution context prior to execution of the CALL block + let ctx_info = self + .decoder + .end_control_block(block.hash().into()) + .expect("no execution context"); // send the end of control block to the chiplets bus to handle the final hash request. self.chiplets.read_hash_result(); // when returning from a function call, reset the context and the free memory pointer to - // what they were before the call - self.system.set_ctx(ctx); - self.system.set_fmp(fmp); + // what they were before the call, and restore the context of the operand stack. + self.system.set_ctx(ctx_info.parent_ctx); + self.system.set_fmp(ctx_info.fmp); + self.stack + .restore_context(ctx_info.stack_depth as usize, ctx_info.next_overflow_addr); // the rest of the VM state does not change self.execute_op(Operation::Noop) @@ -359,7 +380,7 @@ impl Decoder { let clk = self.trace_len() as u32; // append a JOIN row to the execution trace - let parent_addr = self.block_stack.push(addr, BlockType::Join(false)); + let parent_addr = self.block_stack.push(addr, BlockType::Join(false), None); self.trace .append_block_start(parent_addr, Operation::Join, child1_hash, child2_hash); @@ -391,7 +412,7 @@ impl Decoder { let clk = self.trace_len() as u32; // append a SPLIT row to the execution trace - let parent_addr = self.block_stack.push(addr, BlockType::Split); + let parent_addr = self.block_stack.push(addr, BlockType::Split, None); self.trace .append_block_start(parent_addr, Operation::Split, child1_hash, child2_hash); @@ -419,7 +440,9 @@ impl Decoder { // append a LOOP row to the execution trace let enter_loop = stack_top == ONE; - let parent_addr = self.block_stack.push(addr, BlockType::Loop(enter_loop)); + let parent_addr = self + .block_stack + .push(addr, BlockType::Loop(enter_loop), None); self.trace .append_block_start(parent_addr, Operation::Loop, loop_body_hash, [ZERO; 4]); @@ -460,12 +483,12 @@ impl Decoder { /// /// This pushes a block with ID=addr onto the block stack and appends execution of a CALL /// operation to the trace. - pub fn start_call(&mut self, fn_hash: Word, addr: Felt, parent_ctx: u32, parent_fmp: Felt) { + pub fn start_call(&mut self, fn_hash: Word, addr: Felt, ctx_info: ExecutionContextInfo) { // get the current clock cycle here (before the trace table is updated) let clk = self.trace_len() as u32; // push CALL block info onto the block stack and append a CALL row to the execution trace - let parent_addr = self.block_stack.push_call(addr, parent_ctx, parent_fmp); + let parent_addr = self.block_stack.push(addr, BlockType::Call, Some(ctx_info)); self.trace .append_block_start(parent_addr, Operation::Call, fn_hash, [ZERO; 4]); @@ -485,7 +508,7 @@ impl Decoder { /// If the ended block is a CALL block, this method will return values to which execution /// context and free memory pointers were set before the CALL block started executing. For /// non-CALL blocks these values are set to zeros and should be ignored. - pub fn end_control_block(&mut self, block_hash: Word) -> (u32, Felt) { + pub fn end_control_block(&mut self, block_hash: Word) -> Option { // get the current clock cycle here (before the trace table is updated) let clk = self.trace_len() as u32; @@ -504,7 +527,7 @@ impl Decoder { self.debug_info.append_operation(Operation::End); - (block_info.parent_ctx, block_info.parent_fmp) + block_info.ctx_info } // SPAN BLOCK @@ -513,7 +536,7 @@ impl Decoder { /// Starts decoding of a SPAN block defined by the specified operation batches. pub fn start_span(&mut self, first_op_batch: &OpBatch, num_op_groups: Felt, addr: Felt) { debug_assert!(self.span_context.is_none(), "already in span"); - let parent_addr = self.block_stack.push(addr, BlockType::Span); + let parent_addr = self.block_stack.push(addr, BlockType::Span, None); // get the current clock cycle here (before the trace table is updated) let clk = self.trace_len() as u32; diff --git a/processor/src/decoder/tests.rs b/processor/src/decoder/tests.rs index ce6b523ca1..8e5f52c943 100644 --- a/processor/src/decoder/tests.rs +++ b/processor/src/decoder/tests.rs @@ -2,7 +2,10 @@ use super::{ build_op_group, AuxTraceHints, BlockHashTableRow, BlockStackTableRow, BlockTableUpdate, OpGroupTableRow, OpGroupTableUpdate, }; -use crate::{utils::get_trace_len, ExecutionTrace, Felt, Operation, Process, ProgramInputs, Word}; +use crate::{ + decoder::block_stack::ExecutionContextInfo, utils::get_trace_len, ExecutionTrace, Felt, + Operation, Process, ProgramInputs, Word, +}; use rand_utils::rand_value; use vm_core::{ code_blocks::{CodeBlock, Span, OP_BATCH_SIZE}, @@ -894,7 +897,11 @@ fn call_block() { // stack[0] <- fmp // end - let span1 = CodeBlock::new_span(vec![Operation::Push(TWO), Operation::FmpUpdate]); + let span1 = CodeBlock::new_span(vec![ + Operation::Push(TWO), + Operation::FmpUpdate, + Operation::Pad, + ]); let span2 = CodeBlock::new_span(vec![Operation::Push(ONE), Operation::FmpUpdate]); let span3 = CodeBlock::new_span(vec![Operation::FmpAdd]); @@ -914,28 +921,29 @@ fn call_block() { check_op_decoding(&dec_trace, 2, join1_addr, Operation::Span, 2, 0, 0); check_op_decoding(&dec_trace, 3, span1_addr, Operation::Push(TWO), 1, 0, 1); check_op_decoding(&dec_trace, 4, span1_addr, Operation::FmpUpdate, 0, 1, 1); - check_op_decoding(&dec_trace, 5, span1_addr, Operation::End, 0, 0, 0); + check_op_decoding(&dec_trace, 5, span1_addr, Operation::Pad, 0, 2, 1); + check_op_decoding(&dec_trace, 6, span1_addr, Operation::End, 0, 0, 0); // starting CALL block let call_addr = span1_addr + EIGHT; - check_op_decoding(&dec_trace, 6, join1_addr, Operation::Call, 0, 0, 0); + check_op_decoding(&dec_trace, 7, join1_addr, Operation::Call, 0, 0, 0); // starting second SPAN block let span2_addr = call_addr + EIGHT; - check_op_decoding(&dec_trace, 7, call_addr, Operation::Span, 2, 0, 0); - check_op_decoding(&dec_trace, 8, span2_addr, Operation::Push(ONE), 1, 0, 1); - check_op_decoding(&dec_trace, 9, span2_addr, Operation::FmpUpdate, 0, 1, 1); - check_op_decoding(&dec_trace, 10, span2_addr, Operation::End, 0, 0, 0); + check_op_decoding(&dec_trace, 8, call_addr, Operation::Span, 2, 0, 0); + check_op_decoding(&dec_trace, 9, span2_addr, Operation::Push(ONE), 1, 0, 1); + check_op_decoding(&dec_trace, 10, span2_addr, Operation::FmpUpdate, 0, 1, 1); + check_op_decoding(&dec_trace, 11, span2_addr, Operation::End, 0, 0, 0); // ending CALL block - check_op_decoding(&dec_trace, 11, call_addr, Operation::End, 0, 0, 0); + check_op_decoding(&dec_trace, 12, call_addr, Operation::End, 0, 0, 0); // ending internal JOIN block - check_op_decoding(&dec_trace, 12, join1_addr, Operation::End, 0, 0, 0); + check_op_decoding(&dec_trace, 13, join1_addr, Operation::End, 0, 0, 0); // starting the 3rd SPAN block let span3_addr = span2_addr + EIGHT; - check_op_decoding(&dec_trace, 13, INIT_ADDR, Operation::Span, 1, 0, 0); - check_op_decoding(&dec_trace, 14, span3_addr, Operation::FmpAdd, 0, 0, 1); - check_op_decoding(&dec_trace, 15, span3_addr, Operation::End, 0, 0, 0); + check_op_decoding(&dec_trace, 14, INIT_ADDR, Operation::Span, 1, 0, 0); + check_op_decoding(&dec_trace, 15, span3_addr, Operation::FmpAdd, 0, 0, 1); + check_op_decoding(&dec_trace, 16, span3_addr, Operation::End, 0, 0, 0); // ending the program - check_op_decoding(&dec_trace, 16, INIT_ADDR, Operation::End, 0, 0, 0); - check_op_decoding(&dec_trace, 17, ZERO, Operation::Halt, 0, 0, 0); + check_op_decoding(&dec_trace, 17, INIT_ADDR, Operation::End, 0, 0, 0); + check_op_decoding(&dec_trace, 18, ZERO, Operation::Halt, 0, 0, 0); // --- check hasher state columns ------------------------------------------------------------- // in the first row, the hasher state is set to hashes of (join1, span3) @@ -951,38 +959,38 @@ fn call_block() { assert_eq!(fn_block_hash, get_hasher_state2(&dec_trace, 1)); // at the end of the first SPAN, the hasher state is set to the hash of the first child - assert_eq!(span1_hash, get_hasher_state1(&dec_trace, 5)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 5)); + assert_eq!(span1_hash, get_hasher_state1(&dec_trace, 6)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 6)); // in the 7th row, we start the CALL block which hash span2 as its only child let span2_hash: Word = span2.hash().into(); - assert_eq!(span2_hash, get_hasher_state1(&dec_trace, 6)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 5)); + assert_eq!(span2_hash, get_hasher_state1(&dec_trace, 7)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 7)); // span2 ends in the 11th row - assert_eq!(span2_hash, get_hasher_state1(&dec_trace, 10)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 10)); + assert_eq!(span2_hash, get_hasher_state1(&dec_trace, 11)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 11)); // CALL block ends in the 12th row; the second to last element of the hasher state // is set to ONE because we are exiting the CALL block - assert_eq!(fn_block_hash, get_hasher_state1(&dec_trace, 11)); - assert_eq!([ZERO, ZERO, ONE, ZERO], get_hasher_state2(&dec_trace, 11)); + assert_eq!(fn_block_hash, get_hasher_state1(&dec_trace, 12)); + assert_eq!([ZERO, ZERO, ONE, ZERO], get_hasher_state2(&dec_trace, 12)); // internal JOIN block ends in the 13th row - assert_eq!(join1_hash, get_hasher_state1(&dec_trace, 12)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 12)); + assert_eq!(join1_hash, get_hasher_state1(&dec_trace, 13)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 13)); // span3 ends in the 14th row - assert_eq!(span3_hash, get_hasher_state1(&dec_trace, 15)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 15)); + assert_eq!(span3_hash, get_hasher_state1(&dec_trace, 16)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 16)); // the program ends in the 17th row let program_hash: Word = program.hash().into(); - assert_eq!(program_hash, get_hasher_state1(&dec_trace, 16)); - assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 16)); + assert_eq!(program_hash, get_hasher_state1(&dec_trace, 17)); + assert_eq!([ZERO, ZERO, ZERO, ZERO], get_hasher_state2(&dec_trace, 17)); // HALT opcode and program hash gets propagated to the last row - for i in 17..trace_len { + for i in 18..trace_len { assert!(contains_op(&dec_trace, i, Operation::Halt)); assert_eq!(ONE, dec_trace[OP_BIT_EXTRA_COL_IDX][i]); assert_eq!(program_hash, get_hasher_state1(&dec_trace, i)); @@ -991,17 +999,17 @@ fn call_block() { // --- check the ctx column ------------------------------------------------------------------- // for the first 7 cycles, we are in the root context - for i in 0..7 { + for i in 0..8 { assert_eq!(sys_trace[CTX_COL_IDX][i], ZERO); } // when CALL operation is executed, we switch to the new context - for i in 7..12 { - assert_eq!(sys_trace[CTX_COL_IDX][i], Felt::new(7)); + for i in 8..13 { + assert_eq!(sys_trace[CTX_COL_IDX][i], Felt::new(8)); } // once the CALL block exited, we go back to the root context - for i in 12..trace_len { + for i in 13..trace_len { assert_eq!(sys_trace[CTX_COL_IDX][i], ZERO); } @@ -1013,23 +1021,23 @@ fn call_block() { } // when the first FmpUpdate is executed, fmp gets gets incremented by 2 - for i in 5..7 { + for i in 5..8 { assert_eq!(sys_trace[FMP_COL_IDX][i], FMP_MIN + TWO); } // when CALL operation is executed, fmp gets reset to the initial value - for i in 7..10 { + for i in 8..11 { assert_eq!(sys_trace[FMP_COL_IDX][i], FMP_MIN); } // when the second FmpUpdate is executed, fmp gets gets incremented by 1 - for i in 10..12 { + for i in 11..13 { assert_eq!(sys_trace[FMP_COL_IDX][i], FMP_MIN + ONE); } // once the CALL block exited, fmp gets reset back to FMP_MIN + 2, and it remains unchanged // until the end of the trace - for i in 12..trace_len { + for i in 13..trace_len { assert_eq!(sys_trace[FMP_COL_IDX][i], FMP_MIN + TWO); } @@ -1038,24 +1046,25 @@ fn call_block() { (0, BlockTableUpdate::BlockStarted(2)), (1, BlockTableUpdate::BlockStarted(2)), (2, BlockTableUpdate::BlockStarted(0)), - (5, BlockTableUpdate::BlockEnded(true)), - (6, BlockTableUpdate::BlockStarted(1)), - (7, BlockTableUpdate::BlockStarted(0)), - (10, BlockTableUpdate::BlockEnded(false)), + (6, BlockTableUpdate::BlockEnded(true)), + (7, BlockTableUpdate::BlockStarted(1)), + (8, BlockTableUpdate::BlockStarted(0)), (11, BlockTableUpdate::BlockEnded(false)), - (12, BlockTableUpdate::BlockEnded(true)), - (13, BlockTableUpdate::BlockStarted(0)), - (15, BlockTableUpdate::BlockEnded(false)), + (12, BlockTableUpdate::BlockEnded(false)), + (13, BlockTableUpdate::BlockEnded(true)), + (14, BlockTableUpdate::BlockStarted(0)), (16, BlockTableUpdate::BlockEnded(false)), + (17, BlockTableUpdate::BlockEnded(false)), ]; assert_eq!(expected_hints, aux_hints.block_exec_hints()); // --- check block stack table rows ----------------------------------------------------------- + let call_ctx = ExecutionContextInfo::new(0, FMP_MIN + TWO, 17, Felt::new(5)); let expected_rows = vec![ BlockStackTableRow::new_test(INIT_ADDR, ZERO, false), BlockStackTableRow::new_test(join1_addr, INIT_ADDR, false), BlockStackTableRow::new_test(span1_addr, join1_addr, false), - BlockStackTableRow::new_test(call_addr, join1_addr, false), + BlockStackTableRow::new_test_with_ctx(call_addr, join1_addr, false, call_ctx), BlockStackTableRow::new_test(span2_addr, call_addr, false), BlockStackTableRow::new_test(span3_addr, INIT_ADDR, false), ]; diff --git a/processor/src/errors.rs b/processor/src/errors.rs index fbdff93c23..d9ce1b1fae 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -9,15 +9,14 @@ pub enum ExecutionError { AdviceSetLookupFailed(AdviceSetError), AdviceSetNotFound([u8; 32]), AdviceSetUpdateFailed(AdviceSetError), + CodeBlockNotFound(Digest), DivideByZero(u32), EmptyAdviceTape(u32), FailedAssertion(u32), InvalidFmpValue(Felt, Felt), - InvalidPowerOfTwo(Felt), + InvalidStackDepthOnReturn(usize), NotBinaryValue(Felt), NotU32Value(Felt), ProverError(ProverError), - TooManyStackOutputs(usize), UnexecutableCodeBlock(CodeBlock), - CodeBlockNotFound(Digest), }