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}
18
19impl Default for ExecutionOptions {
20    fn default() -> Self {
21        ExecutionOptions {
22            max_cycles: Self::MAX_CYCLES,
23            expected_cycles: MIN_TRACE_LEN as u32,
24            core_trace_fragment_size: Self::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
25            enable_tracing: false,
26            enable_debugging: false,
27        }
28    }
29}
30
31impl ExecutionOptions {
32    // CONSTANTS
33    // --------------------------------------------------------------------------------------------
34
35    /// The maximum number of VM cycles a program is allowed to take.
36    pub const MAX_CYCLES: u32 = 1 << 29;
37
38    /// Default fragment size for core trace generation.
39    pub const DEFAULT_CORE_TRACE_FRAGMENT_SIZE: usize = 4096; // 2^12
40
41    // CONSTRUCTOR
42    // --------------------------------------------------------------------------------------------
43
44    /// Creates a new instance of [ExecutionOptions] from the specified parameters.
45    ///
46    /// If the `max_cycles` is `None` the maximum number of cycles will be set to 2^29.
47    ///
48    /// # Errors
49    /// Returns an error if:
50    /// - `max_cycles` is outside the valid range
51    /// - `expected_cycles` exceeds `max_cycles`
52    /// - `core_trace_fragment_size` is zero
53    pub fn new(
54        max_cycles: Option<u32>,
55        expected_cycles: u32,
56        core_trace_fragment_size: usize,
57        enable_tracing: bool,
58        enable_debugging: bool,
59    ) -> Result<Self, ExecutionOptionsError> {
60        // Validate max cycles.
61        let max_cycles = if let Some(max_cycles) = max_cycles {
62            if max_cycles > Self::MAX_CYCLES {
63                return Err(ExecutionOptionsError::MaxCycleNumTooBig {
64                    max_cycles,
65                    max_cycles_limit: Self::MAX_CYCLES,
66                });
67            }
68            if max_cycles < MIN_TRACE_LEN as u32 {
69                return Err(ExecutionOptionsError::MaxCycleNumTooSmall {
70                    max_cycles,
71                    min_cycles_limit: MIN_TRACE_LEN,
72                });
73            }
74            max_cycles
75        } else {
76            Self::MAX_CYCLES
77        };
78        // Validate expected cycles.
79        if max_cycles < expected_cycles {
80            return Err(ExecutionOptionsError::ExpectedCyclesTooBig {
81                max_cycles,
82                expected_cycles,
83            });
84        }
85        // Round up the expected number of cycles to the next power of two. If it is smaller than
86        // MIN_TRACE_LEN -- pad expected number to it.
87        let expected_cycles = expected_cycles.next_power_of_two().max(MIN_TRACE_LEN as u32);
88
89        // Validate core trace fragment size.
90        if core_trace_fragment_size == 0 {
91            return Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall);
92        }
93
94        Ok(ExecutionOptions {
95            max_cycles,
96            expected_cycles,
97            core_trace_fragment_size,
98            enable_tracing,
99            enable_debugging,
100        })
101    }
102
103    /// Sets the fragment size for core trace generation.
104    ///
105    /// Returns an error if the size is zero.
106    pub fn with_core_trace_fragment_size(
107        mut self,
108        size: usize,
109    ) -> Result<Self, ExecutionOptionsError> {
110        if size == 0 {
111            return Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall);
112        }
113        self.core_trace_fragment_size = size;
114        Ok(self)
115    }
116
117    /// Enables execution of the `trace` instructions.
118    pub fn with_tracing(mut self, enable_tracing: bool) -> Self {
119        self.enable_tracing = enable_tracing;
120        self
121    }
122
123    /// Enables execution of programs in debug mode when the `enable_debugging` flag is set to true;
124    /// otherwise, debug mode is disabled.
125    ///
126    /// In debug mode the VM does the following:
127    /// - Executes `debug` instructions (these are ignored in regular mode).
128    /// - Records additional info about program execution (e.g., keeps track of stack state at every
129    ///   cycle of the VM) which enables stepping through the program forward and backward.
130    pub fn with_debugging(mut self, enable_debugging: bool) -> Self {
131        self.enable_debugging = enable_debugging;
132        self
133    }
134
135    // PUBLIC ACCESSORS
136    // --------------------------------------------------------------------------------------------
137
138    /// Returns maximum number of cycles a program is allowed to execute for.
139    #[inline(always)]
140    pub fn max_cycles(&self) -> u32 {
141        self.max_cycles
142    }
143
144    /// Returns the number of cycles a program is expected to take.
145    ///
146    /// This will serve as a hint to the VM for how much memory to allocate for a program's
147    /// execution trace and may result in performance improvements when the number of expected
148    /// cycles is equal to the number of actual cycles.
149    pub fn expected_cycles(&self) -> u32 {
150        self.expected_cycles
151    }
152
153    /// Returns the fragment size for core trace generation.
154    pub fn core_trace_fragment_size(&self) -> usize {
155        self.core_trace_fragment_size
156    }
157
158    /// Returns a flag indicating whether the VM should execute `trace` instructions.
159    #[inline]
160    pub fn enable_tracing(&self) -> bool {
161        self.enable_tracing
162    }
163
164    /// Returns a flag indicating whether the VM should execute a program in debug mode.
165    #[inline]
166    pub fn enable_debugging(&self) -> bool {
167        self.enable_debugging
168    }
169}
170
171// EXECUTION OPTIONS ERROR
172// ================================================================================================
173
174#[derive(Debug, thiserror::Error)]
175pub enum ExecutionOptionsError {
176    #[error(
177        "expected number of cycles {expected_cycles} must be smaller than the maximum number of cycles {max_cycles}"
178    )]
179    ExpectedCyclesTooBig { max_cycles: u32, expected_cycles: u32 },
180    #[error("maximum number of cycles {max_cycles} must be greater than {min_cycles_limit}")]
181    MaxCycleNumTooSmall { max_cycles: u32, min_cycles_limit: usize },
182    #[error("maximum number of cycles {max_cycles} must be less than {max_cycles_limit}")]
183    MaxCycleNumTooBig { max_cycles: u32, max_cycles_limit: u32 },
184    #[error("core trace fragment size must be greater than 0")]
185    CoreTraceFragmentSizeTooSmall,
186}
187
188// TESTS
189// ================================================================================================
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn valid_fragment_size() {
197        // Valid power of two values should succeed
198        let opts = ExecutionOptions::new(None, 64, 1024, false, false);
199        assert!(opts.is_ok());
200        assert_eq!(opts.unwrap().core_trace_fragment_size(), 1024);
201
202        let opts = ExecutionOptions::new(None, 64, 4096, false, false);
203        assert!(opts.is_ok());
204
205        let opts = ExecutionOptions::new(None, 64, 1, false, false);
206        assert!(opts.is_ok());
207    }
208
209    #[test]
210    fn zero_fragment_size_fails() {
211        let opts = ExecutionOptions::new(None, 64, 0, false, false);
212        assert!(matches!(opts, Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall)));
213    }
214
215    #[test]
216    fn with_core_trace_fragment_size_validates() {
217        // Valid size should succeed
218        let result = ExecutionOptions::default().with_core_trace_fragment_size(2048);
219        assert!(result.is_ok());
220        assert_eq!(result.unwrap().core_trace_fragment_size(), 2048);
221
222        // Zero should fail
223        let result = ExecutionOptions::default().with_core_trace_fragment_size(0);
224        assert!(matches!(result, Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall)));
225    }
226}