Skip to main content

miden_processor/
execution_options.rs

1use miden_air::trace::MIN_TRACE_LEN;
2use miden_core::program::MIN_STACK_DEPTH;
3
4// EXECUTION OPTIONS
5// ================================================================================================
6
7/// A set of parameters specifying execution parameters of the VM.
8///
9/// - `max_cycles` specifies the maximum number of cycles a program is allowed to execute.
10/// - `expected_cycles` specifies the number of cycles a program is expected to execute.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ExecutionOptions {
13    max_cycles: u32,
14    expected_cycles: u32,
15    core_trace_fragment_size: usize,
16    enable_tracing: bool,
17    enable_debugging: bool,
18    /// Maximum number of field elements that can be inserted into the advice map in a single
19    /// `adv.insert_mem` operation.
20    max_adv_map_value_size: usize,
21    /// Maximum total number of field elements allowed in live advice map keys and values.
22    max_adv_map_elements: usize,
23    /// Maximum number of input bytes allowed for a single hash precompile invocation.
24    max_hash_len_bytes: usize,
25    /// Maximum number of continuations allowed on the continuation stack at any point during
26    /// execution.
27    max_num_continuations: usize,
28    /// Maximum number of field elements allowed on the operand stack in the active execution
29    /// context.
30    max_stack_depth: usize,
31}
32
33impl Default for ExecutionOptions {
34    fn default() -> Self {
35        ExecutionOptions {
36            max_cycles: Self::MAX_CYCLES,
37            expected_cycles: MIN_TRACE_LEN as u32,
38            core_trace_fragment_size: Self::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
39            enable_tracing: false,
40            enable_debugging: false,
41            max_adv_map_value_size: Self::DEFAULT_MAX_ADV_MAP_VALUE_SIZE,
42            max_adv_map_elements: Self::DEFAULT_MAX_ADV_MAP_ELEMENTS,
43            max_hash_len_bytes: Self::DEFAULT_MAX_HASH_LEN_BYTES,
44            max_num_continuations: Self::DEFAULT_MAX_NUM_CONTINUATIONS,
45            max_stack_depth: Self::DEFAULT_MAX_STACK_DEPTH,
46        }
47    }
48}
49
50impl ExecutionOptions {
51    // CONSTANTS
52    // --------------------------------------------------------------------------------------------
53
54    /// The maximum number of VM cycles a program is allowed to take.
55    pub const MAX_CYCLES: u32 = 1 << 29;
56
57    /// Default fragment size for core trace generation.
58    pub const DEFAULT_CORE_TRACE_FRAGMENT_SIZE: usize = 4096; // 2^12
59
60    /// Default maximum number of field elements in a single advice map value inserted via
61    /// execution-time advice map mutations. Set to 2^17 (~1 MB given 8-byte field elements).
62    pub const DEFAULT_MAX_ADV_MAP_VALUE_SIZE: usize = 1 << 17;
63
64    /// Default maximum total number of field elements in live advice map keys and values.
65    ///
66    /// Set to 2^20 so the default allows multiple maximum-sized entries while still providing a
67    /// finite host-memory backstop. Each entry contributes 4 key elements plus its value length.
68    pub const DEFAULT_MAX_ADV_MAP_ELEMENTS: usize = 1 << 20;
69
70    /// Default maximum number of input bytes for a single hash precompile invocation (e.g.
71    /// keccak256, sha512, etc.). Set to 2^20 (1 MB).
72    pub const DEFAULT_MAX_HASH_LEN_BYTES: usize = 1 << 20;
73
74    /// Default maximum number of continuations allowed on the continuation stack.
75    /// Set to 2^16 (65536).
76    pub const DEFAULT_MAX_NUM_CONTINUATIONS: usize = 1 << 16;
77
78    /// Default maximum number of field elements allowed on the operand stack.
79    ///
80    /// This preserves the effective stack depth ceiling imposed by the previous fixed
81    /// `FastProcessor` stack buffer.
82    pub const DEFAULT_MAX_STACK_DEPTH: usize = 6615;
83
84    // CONSTRUCTOR
85    // --------------------------------------------------------------------------------------------
86
87    /// Creates a new instance of [ExecutionOptions] from the specified parameters.
88    ///
89    /// If the `max_cycles` is `None` the maximum number of cycles will be set to 2^29.
90    ///
91    /// # Errors
92    /// Returns an error if:
93    /// - `max_cycles` is outside the valid range
94    /// - after rounding up to the next power of two, `expected_cycles` exceeds `max_cycles`
95    /// - `core_trace_fragment_size` is zero
96    pub fn new(
97        max_cycles: Option<u32>,
98        expected_cycles: u32,
99        core_trace_fragment_size: usize,
100        enable_tracing: bool,
101        enable_debugging: bool,
102    ) -> Result<Self, ExecutionOptionsError> {
103        // Validate max cycles.
104        let max_cycles = if let Some(max_cycles) = max_cycles {
105            if max_cycles > Self::MAX_CYCLES {
106                return Err(ExecutionOptionsError::MaxCycleNumTooBig {
107                    max_cycles,
108                    max_cycles_limit: Self::MAX_CYCLES,
109                });
110            }
111            if max_cycles < MIN_TRACE_LEN as u32 {
112                return Err(ExecutionOptionsError::MaxCycleNumTooSmall {
113                    max_cycles,
114                    min_cycles_limit: MIN_TRACE_LEN,
115                });
116            }
117            max_cycles
118        } else {
119            Self::MAX_CYCLES
120        };
121        // Round up the expected number of cycles to the next power of two. If it is smaller than
122        // MIN_TRACE_LEN -- pad expected number to it.
123        let expected_cycles = expected_cycles.next_power_of_two().max(MIN_TRACE_LEN as u32);
124        // Validate expected cycles (after rounding) against max_cycles.
125        if max_cycles < expected_cycles {
126            return Err(ExecutionOptionsError::ExpectedCyclesTooBig {
127                max_cycles,
128                expected_cycles,
129            });
130        }
131
132        // Validate core trace fragment size.
133        if core_trace_fragment_size == 0 {
134            return Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall);
135        }
136
137        Ok(ExecutionOptions {
138            max_cycles,
139            expected_cycles,
140            core_trace_fragment_size,
141            enable_tracing,
142            enable_debugging,
143            max_adv_map_value_size: Self::DEFAULT_MAX_ADV_MAP_VALUE_SIZE,
144            max_adv_map_elements: Self::DEFAULT_MAX_ADV_MAP_ELEMENTS,
145            max_hash_len_bytes: Self::DEFAULT_MAX_HASH_LEN_BYTES,
146            max_num_continuations: Self::DEFAULT_MAX_NUM_CONTINUATIONS,
147            max_stack_depth: Self::DEFAULT_MAX_STACK_DEPTH,
148        })
149    }
150
151    /// Sets the fragment size for core trace generation.
152    ///
153    /// Returns an error if the size is zero.
154    pub fn with_core_trace_fragment_size(
155        mut self,
156        size: usize,
157    ) -> Result<Self, ExecutionOptionsError> {
158        if size == 0 {
159            return Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall);
160        }
161        self.core_trace_fragment_size = size;
162        Ok(self)
163    }
164
165    /// Enables execution of the `trace` instructions.
166    pub fn with_tracing(mut self, enable_tracing: bool) -> Self {
167        self.enable_tracing = enable_tracing;
168        self
169    }
170
171    /// Enables execution of programs in debug mode when the `enable_debugging` flag is set to true;
172    /// otherwise, debug mode is disabled.
173    ///
174    /// In debug mode the VM does the following:
175    /// - Executes `debug` instructions (these are ignored in regular mode).
176    /// - Records additional info about program execution (e.g., keeps track of stack state at every
177    ///   cycle of the VM) which enables stepping through the program forward and backward.
178    pub fn with_debugging(mut self, enable_debugging: bool) -> Self {
179        self.enable_debugging = enable_debugging;
180        self
181    }
182
183    // PUBLIC ACCESSORS
184    // --------------------------------------------------------------------------------------------
185
186    /// Returns maximum number of cycles a program is allowed to execute for.
187    #[inline(always)]
188    pub fn max_cycles(&self) -> u32 {
189        self.max_cycles
190    }
191
192    /// Returns the number of cycles a program is expected to take.
193    ///
194    /// This will serve as a hint to the VM for how much memory to allocate for a program's
195    /// execution trace and may result in performance improvements when the number of expected
196    /// cycles is equal to the number of actual cycles.
197    pub fn expected_cycles(&self) -> u32 {
198        self.expected_cycles
199    }
200
201    /// Returns the fragment size for core trace generation.
202    pub fn core_trace_fragment_size(&self) -> usize {
203        self.core_trace_fragment_size
204    }
205
206    /// Returns a flag indicating whether the VM should execute `trace` instructions.
207    #[inline]
208    pub fn enable_tracing(&self) -> bool {
209        self.enable_tracing
210    }
211
212    /// Returns a flag indicating whether the VM should execute a program in debug mode.
213    #[inline]
214    pub fn enable_debugging(&self) -> bool {
215        self.enable_debugging
216    }
217
218    /// Returns the maximum number of field elements allowed in a single live advice map value.
219    #[inline]
220    pub fn max_adv_map_value_size(&self) -> usize {
221        self.max_adv_map_value_size
222    }
223
224    /// Returns the maximum total number of field elements allowed in live advice map keys and
225    /// values.
226    #[inline]
227    pub fn max_adv_map_elements(&self) -> usize {
228        self.max_adv_map_elements
229    }
230
231    /// Returns the maximum number of input bytes allowed for a single hash precompile invocation.
232    #[inline]
233    pub fn max_hash_len_bytes(&self) -> usize {
234        self.max_hash_len_bytes
235    }
236
237    /// Sets the maximum number of field elements allowed in a single live advice map value.
238    pub fn with_max_adv_map_value_size(mut self, size: usize) -> Self {
239        self.max_adv_map_value_size = size;
240        self
241    }
242
243    /// Sets the maximum total number of field elements allowed in live advice map keys and values.
244    pub fn with_max_adv_map_elements(mut self, size: usize) -> Self {
245        self.max_adv_map_elements = size;
246        self
247    }
248
249    /// Sets the maximum number of input bytes allowed for a single hash precompile invocation.
250    pub fn with_max_hash_len_bytes(mut self, size: usize) -> Self {
251        self.max_hash_len_bytes = size;
252        self
253    }
254
255    /// Returns the maximum number of continuations allowed on the continuation stack.
256    #[inline]
257    pub fn max_num_continuations(&self) -> usize {
258        self.max_num_continuations
259    }
260
261    /// Returns the maximum number of field elements allowed on the operand stack in the active
262    /// execution context.
263    #[inline]
264    pub fn max_stack_depth(&self) -> usize {
265        self.max_stack_depth
266    }
267
268    /// Sets the maximum number of continuations allowed on the continuation stack.
269    pub fn with_max_num_continuations(mut self, max_num_continuations: usize) -> Self {
270        self.max_num_continuations = max_num_continuations;
271        self
272    }
273
274    /// Sets the maximum number of field elements allowed on the operand stack in the active
275    /// execution context.
276    pub fn with_max_stack_depth(
277        mut self,
278        max_stack_depth: usize,
279    ) -> Result<Self, ExecutionOptionsError> {
280        if max_stack_depth < MIN_STACK_DEPTH {
281            return Err(ExecutionOptionsError::MaxStackDepthTooSmall {
282                max_stack_depth,
283                min_stack_depth: MIN_STACK_DEPTH,
284            });
285        }
286        self.max_stack_depth = max_stack_depth;
287        Ok(self)
288    }
289}
290
291// EXECUTION OPTIONS ERROR
292// ================================================================================================
293
294#[derive(Debug, thiserror::Error)]
295pub enum ExecutionOptionsError {
296    #[error(
297        "expected number of cycles {expected_cycles} must be smaller than the maximum number of cycles {max_cycles}"
298    )]
299    ExpectedCyclesTooBig { max_cycles: u32, expected_cycles: u32 },
300    #[error("maximum number of cycles {max_cycles} must be greater than {min_cycles_limit}")]
301    MaxCycleNumTooSmall { max_cycles: u32, min_cycles_limit: usize },
302    #[error("maximum number of cycles {max_cycles} must be less than {max_cycles_limit}")]
303    MaxCycleNumTooBig { max_cycles: u32, max_cycles_limit: u32 },
304    #[error("core trace fragment size must be greater than 0")]
305    CoreTraceFragmentSizeTooSmall,
306    #[error("maximum stack depth {max_stack_depth} must be at least {min_stack_depth}")]
307    MaxStackDepthTooSmall {
308        max_stack_depth: usize,
309        min_stack_depth: usize,
310    },
311}
312
313// TESTS
314// ================================================================================================
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn valid_fragment_size() {
322        // Valid power of two values should succeed
323        let opts = ExecutionOptions::new(None, 64, 1024, false, false);
324        assert!(opts.is_ok());
325        assert_eq!(opts.unwrap().core_trace_fragment_size(), 1024);
326
327        let opts = ExecutionOptions::new(None, 64, 4096, false, false);
328        assert!(opts.is_ok());
329
330        let opts = ExecutionOptions::new(None, 64, 1, false, false);
331        assert!(opts.is_ok());
332    }
333
334    #[test]
335    fn zero_fragment_size_fails() {
336        let opts = ExecutionOptions::new(None, 64, 0, false, false);
337        assert!(matches!(opts, Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall)));
338    }
339
340    #[test]
341    fn with_core_trace_fragment_size_validates() {
342        // Valid size should succeed
343        let result = ExecutionOptions::default().with_core_trace_fragment_size(2048);
344        assert!(result.is_ok());
345        assert_eq!(result.unwrap().core_trace_fragment_size(), 2048);
346
347        // Zero should fail
348        let result = ExecutionOptions::default().with_core_trace_fragment_size(0);
349        assert!(matches!(result, Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall)));
350    }
351
352    #[test]
353    fn expected_cycles_validated_after_rounding() {
354        // expected_cycles=65 rounds to 128; max_cycles=100 -> must fail (128 > 100).
355        let opts = ExecutionOptions::new(Some(100), 65, 1024, false, false);
356        assert!(matches!(
357            opts,
358            Err(ExecutionOptionsError::ExpectedCyclesTooBig {
359                max_cycles: 100,
360                expected_cycles: 128
361            })
362        ));
363
364        // expected_cycles=64 rounds to 64; max_cycles=100 -> ok.
365        let opts = ExecutionOptions::new(Some(100), 64, 1024, false, false);
366        assert!(opts.is_ok());
367        assert_eq!(opts.unwrap().expected_cycles(), 64);
368    }
369
370    #[test]
371    fn max_stack_depth_validates_minimum_depth() {
372        let result = ExecutionOptions::default().with_max_stack_depth(MIN_STACK_DEPTH - 1);
373        assert!(matches!(
374            result,
375            Err(ExecutionOptionsError::MaxStackDepthTooSmall {
376                max_stack_depth,
377                min_stack_depth: MIN_STACK_DEPTH,
378            }) if max_stack_depth == MIN_STACK_DEPTH - 1
379        ));
380
381        let result = ExecutionOptions::default().with_max_stack_depth(MIN_STACK_DEPTH);
382        assert!(result.is_ok());
383        assert_eq!(result.unwrap().max_stack_depth(), MIN_STACK_DEPTH);
384    }
385}