From c7bfa7081b94881ae3fdf2060fa1c90bc45f3d5b Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Tue, 4 Jul 2023 04:00:46 +0300 Subject: [PATCH] feat: impl cycle limit and expected memory consumption --- CHANGELOG.md | 1 + air/src/options.rs | 10 +++++++++ miden/src/cli/run.rs | 2 +- processor/src/chiplets/tests.rs | 7 ++++--- processor/src/decoder/tests.rs | 10 +++++---- processor/src/decorators/tests.rs | 8 ++++--- processor/src/errors.rs | 4 ++++ processor/src/lib.rs | 35 ++++++++++++++++++++++++------- processor/src/operations/mod.rs | 21 ++++++++++++++++--- processor/src/trace/tests/mod.rs | 8 ++++--- stdlib/tests/mem/mod.rs | 5 +++-- test-utils/src/lib.rs | 16 ++++++++++---- 12 files changed, 96 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e6998269a..e827e8f9f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ #### VM Internals - Simplified range checker and removed 1 main and 1 auxiliary trace column (#949). - Added `get_mapped_values()` and `get_store_subset()` methods to the `AdviceProvider` trait (#987). +- Added options to specify maximum number of cycles and expected number of cycles for a program (#998). ## 0.6.1 (2023-06-29) diff --git a/air/src/options.rs b/air/src/options.rs index b94e3536c4..014edef5ec 100644 --- a/air/src/options.rs +++ b/air/src/options.rs @@ -166,4 +166,14 @@ impl ExecutionOptions { expected_cycles, }) } + + /// Returns maximum number of cycles + pub fn max_cycles(&self) -> Option { + self.max_cycles + } + + /// Returns number of the expected cycles + pub fn expected_cycles(&self) -> u32 { + self.expected_cycles + } } diff --git a/miden/src/cli/run.rs b/miden/src/cli/run.rs index 78535d63e9..2ec6253f3f 100644 --- a/miden/src/cli/run.rs +++ b/miden/src/cli/run.rs @@ -64,7 +64,7 @@ impl RunCmd { // execute program and generate outputs let trace = processor::execute(&program, stack_inputs, advice_provider, execution_options) - .map_err(|err| format!("Failed to generate exection trace = {:?}", err))?; + .map_err(|err| format!("Failed to generate exection trace = {:?}", err.to_string()))?; println!("done ({} steps in {} ms)", trace.get_trace_len(), now.elapsed().as_millis()); diff --git a/processor/src/chiplets/tests.rs b/processor/src/chiplets/tests.rs index 545c9d3f16..aec54bd884 100644 --- a/processor/src/chiplets/tests.rs +++ b/processor/src/chiplets/tests.rs @@ -1,6 +1,6 @@ use crate::{ - utils::get_trace_len, CodeBlock, ExecutionTrace, Kernel, MemAdviceProvider, Operation, Process, - StackInputs, Vec, + utils::get_trace_len, CodeBlock, ExecutionOptions, ExecutionTrace, Kernel, MemAdviceProvider, + Operation, Process, StackInputs, Vec, }; use miden_air::trace::{ chiplets::{ @@ -112,7 +112,8 @@ fn build_trace( ) -> (ChipletsTrace, usize) { let stack_inputs = StackInputs::try_from_values(stack_inputs.iter().copied()).unwrap(); let advice_provider = MemAdviceProvider::default(); - let mut process = Process::new(kernel, stack_inputs, advice_provider); + let mut process = + Process::new(kernel, stack_inputs, advice_provider, ExecutionOptions::default()); let program = CodeBlock::new_span(operations); process.execute_code_block(&program, &CodeBlockTable::default()).unwrap(); diff --git a/processor/src/decoder/tests.rs b/processor/src/decoder/tests.rs index df3b9b21e5..e65d346680 100644 --- a/processor/src/decoder/tests.rs +++ b/processor/src/decoder/tests.rs @@ -1,7 +1,7 @@ use super::{ super::{ - utils::get_trace_len, ExecutionTrace, Felt, Kernel, MemAdviceProvider, Operation, Process, - StackInputs, Word, + utils::get_trace_len, ExecutionOptions, ExecutionTrace, Felt, Kernel, MemAdviceProvider, + Operation, Process, StackInputs, Word, }, build_op_group, AuxTraceHints, BlockHashTableRow, BlockStackTableRow, BlockTableUpdate, ExecutionContextInfo, OpGroupTableRow, OpGroupTableUpdate, @@ -1500,7 +1500,8 @@ fn set_user_op_helpers_many() { fn build_trace(stack_inputs: &[u64], program: &CodeBlock) -> (DecoderTrace, AuxTraceHints, usize) { let stack_inputs = StackInputs::try_from_values(stack_inputs.iter().copied()).unwrap(); let advice_provider = MemAdviceProvider::default(); - let mut process = Process::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = + Process::new(Kernel::default(), stack_inputs, advice_provider, ExecutionOptions::default()); process.execute_code_block(program, &CodeBlockTable::default()).unwrap(); let (trace, aux_hints) = ExecutionTrace::test_finalize_trace(process); @@ -1527,7 +1528,8 @@ fn build_call_trace( }; let advice_provider = MemAdviceProvider::default(); let stack_inputs = crate::StackInputs::default(); - let mut process = Process::new(kernel, stack_inputs, advice_provider); + let mut process = + Process::new(kernel, stack_inputs, advice_provider, ExecutionOptions::default()); // build code block table let mut cb_table = CodeBlockTable::default(); diff --git a/processor/src/decorators/tests.rs b/processor/src/decorators/tests.rs index 88623cd7c7..17aee7542c 100644 --- a/processor/src/decorators/tests.rs +++ b/processor/src/decorators/tests.rs @@ -1,5 +1,5 @@ use super::{ - super::{AdviceInputs, Felt, FieldElement, Kernel, Operation, StarkField}, + super::{AdviceInputs, ExecutionOptions, Felt, FieldElement, Kernel, Operation, StarkField}, Process, }; use crate::{MemAdviceProvider, StackInputs, Word}; @@ -30,7 +30,8 @@ fn push_merkle_node() { let stack_inputs = StackInputs::try_from_values(stack_inputs).unwrap(); let advice_inputs = AdviceInputs::default().with_merkle_store(store); let advice_provider = MemAdviceProvider::from(advice_inputs); - let mut process = Process::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = + Process::new(Kernel::default(), stack_inputs, advice_provider, ExecutionOptions::default()); process.execute_op(Operation::Noop).unwrap(); // push the node onto the advice stack @@ -185,7 +186,8 @@ fn assert_case_smtget( .with_merkle_store(store) .with_map([(node.into_bytes(), mapped)]); let advice_provider = MemAdviceProvider::from(advice_inputs); - let mut process = Process::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = + Process::new(Kernel::default(), stack_inputs, advice_provider, ExecutionOptions::default()); // call the injector and clear the stack process.execute_op(Operation::Noop).unwrap(); diff --git a/processor/src/errors.rs b/processor/src/errors.rs index d6a10de95a..408558da98 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -19,6 +19,7 @@ pub enum ExecutionError { AdviceStackReadFailed(u32), CallerNotInSyscall, CodeBlockNotFound(Digest), + CycleLimitExceeded(u32), DivideByZero(u32), Ext2InttError(Ext2InttError), FailedAssertion(u32), @@ -61,6 +62,9 @@ impl Display for ExecutionError { "Failed to execute code block with root {hex}; the block could not be found" ) } + CycleLimitExceeded(max_cycles) => { + write!(f, "Exceeded the allowed number of cycles which equals {max_cycles}") + } DivideByZero(clk) => write!(f, "Division by zero at clock cycle {clk}"), Ext2InttError(err) => write!(f, "Failed to execute Ext2Intt operation: {err}"), FailedAssertion(clk) => write!(f, "Assertion failed at clock cycle {clk}"), diff --git a/processor/src/lib.rs b/processor/src/lib.rs index c6eb96107f..35ba0f84fc 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -112,13 +112,13 @@ pub fn execute( program: &Program, stack_inputs: StackInputs, advice_provider: A, - _options: ExecutionOptions, + options: ExecutionOptions, ) -> Result where A: AdviceProvider, { - let mut process = Process::new(program.kernel().clone(), stack_inputs, advice_provider); - // TODO: use ExecutionOptions to limit program execution + let mut process = + Process::new(program.kernel().clone(), stack_inputs, advice_provider, options); let stack_outputs = process.execute(program)?; let trace = ExecutionTrace::new(process, stack_outputs); assert_eq!(&program.hash(), trace.program_hash(), "inconsistent program hash"); @@ -161,6 +161,7 @@ where range: RangeChecker, chiplets: Chiplets, advice_provider: A, + max_cycles: Option, } impl Process @@ -170,13 +171,18 @@ where // CONSTRUCTORS // -------------------------------------------------------------------------------------------- /// Creates a new process with the provided inputs. - pub fn new(kernel: Kernel, stack_inputs: StackInputs, advice_provider: A) -> Self { - Self::initialize(kernel, stack_inputs, advice_provider, false) + pub fn new( + kernel: Kernel, + stack_inputs: StackInputs, + advice_provider: A, + execution_options: ExecutionOptions, + ) -> Self { + Self::initialize(kernel, stack_inputs, advice_provider, false, execution_options) } /// Creates a new process with provided inputs and debug options enabled. pub fn new_debug(kernel: Kernel, stack_inputs: StackInputs, advice_provider: A) -> Self { - Self::initialize(kernel, stack_inputs, advice_provider, true) + Self::initialize(kernel, stack_inputs, advice_provider, true, ExecutionOptions::default()) } fn initialize( @@ -184,14 +190,16 @@ where stack: StackInputs, advice_provider: A, in_debug_mode: bool, + execution_options: ExecutionOptions, ) -> Self { Self { - system: System::new(MIN_TRACE_LEN), + system: System::new(execution_options.expected_cycles() as usize), decoder: Decoder::new(in_debug_mode), - stack: Stack::new(&stack, MIN_TRACE_LEN, in_debug_mode), + stack: Stack::new(&stack, execution_options.expected_cycles() as usize, in_debug_mode), range: RangeChecker::new(), chiplets: Chiplets::new(kernel), advice_provider, + max_cycles: execution_options.max_cycles(), } } @@ -218,6 +226,7 @@ where block: &CodeBlock, cb_table: &CodeBlockTable, ) -> Result<(), ExecutionError> { + self.check_cycles_limit()?; match block { CodeBlock::Join(block) => self.execute_join_block(block, cb_table), CodeBlock::Split(block) => self.execute_split_block(block, cb_table), @@ -346,6 +355,7 @@ where op_offset += op_batch.ops().len(); } + self.check_cycles_limit()?; self.end_span_block(block) } @@ -436,6 +446,14 @@ where Ok(()) } + fn check_cycles_limit(&self) -> Result<(), ExecutionError> { + if self.max_cycles.is_some_and(|max_cycles| self.system.clk() > max_cycles) { + Err(ExecutionError::CycleLimitExceeded(self.max_cycles.unwrap())) + } else { + Ok(()) + } + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -473,4 +491,5 @@ where pub range: RangeChecker, pub chiplets: Chiplets, pub advice_provider: A, + pub max_cycles: Option, } diff --git a/processor/src/operations/mod.rs b/processor/src/operations/mod.rs index f4fe6e95a7..d77c0d7a93 100644 --- a/processor/src/operations/mod.rs +++ b/processor/src/operations/mod.rs @@ -178,7 +178,12 @@ impl Process { /// initialized with the provided values. fn new_dummy(stack_inputs: super::StackInputs) -> Self { let advice_provider = super::MemAdviceProvider::default(); - let mut process = Self::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = Self::new( + Kernel::default(), + stack_inputs, + advice_provider, + super::ExecutionOptions::default(), + ); process.execute_op(Operation::Noop).unwrap(); process } @@ -196,7 +201,12 @@ impl Process { .with_stack_values(advice_stack.iter().copied()) .unwrap(); let advice_provider = super::MemAdviceProvider::from(advice_inputs); - let mut process = Self::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = Self::new( + Kernel::default(), + stack_inputs, + advice_provider, + super::ExecutionOptions::default(), + ); process.execute_op(Operation::Noop).unwrap(); process } @@ -224,7 +234,12 @@ impl Process { advice_inputs: super::AdviceInputs, ) -> Self { let advice_provider = super::MemAdviceProvider::from(advice_inputs); - let mut process = Self::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = Self::new( + Kernel::default(), + stack_inputs, + advice_provider, + super::ExecutionOptions::default(), + ); process.decoder.add_dummy_trace_row(); process.execute_op(Operation::Noop).unwrap(); process diff --git a/processor/src/trace/tests/mod.rs b/processor/src/trace/tests/mod.rs index ba7c880299..1a81e9296b 100644 --- a/processor/src/trace/tests/mod.rs +++ b/processor/src/trace/tests/mod.rs @@ -2,7 +2,7 @@ use super::{ super::chiplets::init_state_from_words, ExecutionTrace, Felt, FieldElement, LookupTableRow, Process, Trace, Vec, NUM_RAND_ROWS, }; -use crate::{AdviceInputs, MemAdviceProvider, StackInputs}; +use crate::{AdviceInputs, ExecutionOptions, MemAdviceProvider, StackInputs}; use rand_utils::rand_array; use vm_core::{ code_blocks::CodeBlock, CodeBlockTable, Kernel, Operation, StackOutputs, Word, ONE, ZERO, @@ -20,7 +20,8 @@ mod stack; pub fn build_trace_from_block(program: &CodeBlock, stack_inputs: &[u64]) -> ExecutionTrace { let stack_inputs = StackInputs::try_from_values(stack_inputs.iter().copied()).unwrap(); let advice_provider = MemAdviceProvider::default(); - let mut process = Process::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = + Process::new(Kernel::default(), stack_inputs, advice_provider, ExecutionOptions::default()); process.execute_code_block(program, &CodeBlockTable::default()).unwrap(); ExecutionTrace::new(process, StackOutputs::default()) } @@ -41,7 +42,8 @@ pub fn build_trace_from_ops_with_inputs( advice_inputs: AdviceInputs, ) -> ExecutionTrace { let advice_provider = MemAdviceProvider::from(advice_inputs); - let mut process = Process::new(Kernel::default(), stack_inputs, advice_provider); + let mut process = + Process::new(Kernel::default(), stack_inputs, advice_provider, ExecutionOptions::default()); let program = CodeBlock::new_span(operations); process.execute_code_block(&program, &CodeBlockTable::default()).unwrap(); ExecutionTrace::new(process, StackOutputs::default()) diff --git a/stdlib/tests/mem/mod.rs b/stdlib/tests/mem/mod.rs index c390a2644a..b61aa4e915 100644 --- a/stdlib/tests/mem/mod.rs +++ b/stdlib/tests/mem/mod.rs @@ -1,6 +1,6 @@ use test_utils::{ - build_expected_hash, build_expected_perm, stack_to_ints, AdviceInputs, MemAdviceProvider, - Process, StackInputs, ONE, ZERO, + build_expected_hash, build_expected_perm, stack_to_ints, AdviceInputs, ExecutionOptions, + MemAdviceProvider, Process, StackInputs, ONE, ZERO, }; #[test] @@ -31,6 +31,7 @@ fn test_memcopy() { program.kernel().clone(), StackInputs::default(), MemAdviceProvider::from(AdviceInputs::default()), + ExecutionOptions::default(), ); process.execute(&program).unwrap(); diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 1846f2a7ef..1c3249f8a7 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -164,8 +164,12 @@ impl Test { let advice_provider = MemAdviceProvider::from(self.advice_inputs.clone()); // execute the test - let mut process = - Process::new(program.kernel().clone(), self.stack_inputs.clone(), advice_provider); + let mut process = Process::new( + program.kernel().clone(), + self.stack_inputs.clone(), + advice_provider, + ExecutionOptions::default(), + ); process.execute(&program).unwrap(); // validate the memory state @@ -236,8 +240,12 @@ impl Test { pub fn execute_process(&self) -> Result, ExecutionError> { let program = self.compile(); let advice_provider = MemAdviceProvider::from(self.advice_inputs.clone()); - let mut process = - Process::new(program.kernel().clone(), self.stack_inputs.clone(), advice_provider); + let mut process = Process::new( + program.kernel().clone(), + self.stack_inputs.clone(), + advice_provider, + ExecutionOptions::default(), + ); process.execute(&program)?; Ok(process) }