Skip to main content

miden_processor/
execution_options.rs

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