miden_processor/execution_options.rs
1use miden_air::trace::MIN_TRACE_LEN;
2use miden_core::{
3 deferred::DEFAULT_MAX_DEFERRED_ELEMENTS as DEFAULT_DEFERRED_STATE_ELEMENTS,
4 program::MIN_STACK_DEPTH,
5};
6
7// EXECUTION OPTIONS
8// ================================================================================================
9
10/// A set of parameters specifying execution parameters of the VM.
11///
12/// - `max_cycles` specifies the maximum number of cycles a program is allowed to execute.
13/// - `expected_cycles` specifies the number of cycles a program is expected to execute.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct ExecutionOptions {
16 max_cycles: u32,
17 expected_cycles: u32,
18 core_trace_fragment_size: usize,
19 /// Maximum number of field elements that can be inserted into the advice map in a single
20 /// `adv.insert_mem` operation.
21 max_adv_map_value_size: usize,
22 /// Maximum total number of field elements allowed in live advice map keys and values.
23 max_adv_map_elements: usize,
24 /// Whether the synchronous prover is asked to overlap hasher-chiplet trace building with
25 /// program execution (std-only; the sequential path is used on no_std regardless).
26 overlapped_trace_build: bool,
27 /// Maximum number of input bytes allowed for a single hash precompile invocation.
28 max_hash_len_bytes: usize,
29 /// Maximum approximate number of field elements allowed in durable deferred-state nodes.
30 max_deferred_elements: usize,
31 /// Maximum number of continuations allowed on the continuation stack at any point during
32 /// execution.
33 max_num_continuations: usize,
34 /// Maximum number of internal nodes allowed in the advice provider's Merkle store.
35 max_merkle_store_nodes: usize,
36 /// Maximum number of field elements allowed on the operand stack across the active execution
37 /// context and all suspended contexts.
38 ///
39 /// A `call`, `dyncall`, or `syscall` context switch hides the caller's operand-stack overflow
40 /// (everything below the top 16 elements) until the callee returns. This limit bounds the
41 /// aggregate of the active context's depth plus all such suspended overflow, so nesting
42 /// context switches cannot accumulate hidden operand-stack memory beyond the configured
43 /// budget.
44 max_stack_depth: usize,
45 /// Maximum number of field elements allowed in the processor's memory at any point during
46 /// execution, rounded up to the nearest multiple of 4.
47 max_memory_elements: usize,
48}
49
50impl Default for ExecutionOptions {
51 fn default() -> Self {
52 ExecutionOptions {
53 max_cycles: Self::MAX_CYCLES,
54 expected_cycles: MIN_TRACE_LEN as u32,
55 core_trace_fragment_size: Self::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
56 max_adv_map_value_size: Self::DEFAULT_MAX_ADV_MAP_VALUE_SIZE,
57 max_adv_map_elements: Self::DEFAULT_MAX_ADV_MAP_ELEMENTS,
58 max_hash_len_bytes: Self::DEFAULT_MAX_HASH_LEN_BYTES,
59 max_deferred_elements: Self::DEFAULT_MAX_DEFERRED_ELEMENTS,
60 max_num_continuations: Self::DEFAULT_MAX_NUM_CONTINUATIONS,
61 max_merkle_store_nodes: Self::DEFAULT_MAX_MERKLE_STORE_NODES,
62 max_stack_depth: Self::DEFAULT_MAX_STACK_DEPTH,
63 max_memory_elements: Self::DEFAULT_MAX_MEMORY_ELEMENTS,
64 overlapped_trace_build: true,
65 }
66 }
67}
68
69impl ExecutionOptions {
70 // CONSTANTS
71 // --------------------------------------------------------------------------------------------
72
73 /// The maximum number of VM cycles a program is allowed to take.
74 pub const MAX_CYCLES: u32 = 1 << 29;
75
76 /// Default fragment size for core trace generation.
77 pub const DEFAULT_CORE_TRACE_FRAGMENT_SIZE: usize = 4096; // 2^12
78
79 /// Default maximum number of field elements in a single advice map value inserted via
80 /// execution-time advice map mutations. Set to 2^17 (~1 MB given 8-byte field elements).
81 pub const DEFAULT_MAX_ADV_MAP_VALUE_SIZE: usize = 1 << 17;
82
83 /// Default maximum total number of field elements in live advice map keys and values.
84 ///
85 /// Set to 2^20 so the default allows multiple maximum-sized entries while still providing a
86 /// finite host-memory backstop. Each entry contributes 4 key elements plus its value length.
87 pub const DEFAULT_MAX_ADV_MAP_ELEMENTS: usize = 1 << 20;
88
89 /// Default maximum number of input bytes for a single hash precompile invocation.
90 /// Set to 2^20 (1 MB).
91 pub const DEFAULT_MAX_HASH_LEN_BYTES: usize = 1 << 20;
92
93 /// Default maximum approximate number of field elements allowed in deferred state.
94 pub const DEFAULT_MAX_DEFERRED_ELEMENTS: usize = DEFAULT_DEFERRED_STATE_ELEMENTS;
95
96 /// Default maximum number of continuations allowed on the continuation stack.
97 /// Set to 2^16 (65536).
98 pub const DEFAULT_MAX_NUM_CONTINUATIONS: usize = 1 << 16;
99
100 /// Default maximum number of internal nodes allowed in the advice provider's Merkle store.
101 ///
102 /// Set to 2^20 so the default allows large Merkle inputs and repeated updates while still
103 /// providing a finite host-memory backstop.
104 pub const DEFAULT_MAX_MERKLE_STORE_NODES: usize = 1 << 20;
105
106 /// Default maximum number of field elements allowed on the operand stack.
107 ///
108 /// This preserves the effective stack depth ceiling imposed by the previous fixed
109 /// `FastProcessor` stack buffer.
110 pub const DEFAULT_MAX_STACK_DEPTH: usize = 6615;
111
112 /// Default maximum number of field elements allowed in the processor's memory.
113 ///
114 /// Memory is element-addressable, so this bounds the total number of elements live across all
115 /// contexts. Internally memory is stored at word granularity (4 elements per word), so the
116 /// effective limit is rounded up to a whole number of words. Set to 2^28, which lets programs
117 /// use a large amount of memory while still providing a finite host-memory backstop against
118 /// unbounded growth from writes to arbitrarily many unique addresses.
119 pub const DEFAULT_MAX_MEMORY_ELEMENTS: usize = 1 << 28;
120
121 // CONSTRUCTOR
122 // --------------------------------------------------------------------------------------------
123
124 /// Creates a new instance of [ExecutionOptions] from the specified parameters.
125 ///
126 /// If the `max_cycles` is `None` the maximum number of cycles will be set to 2^29.
127 ///
128 /// # Errors
129 /// Returns an error if:
130 /// - `max_cycles` is outside the valid range
131 /// - after rounding up to the next power of two, `expected_cycles` exceeds `max_cycles`
132 /// - `core_trace_fragment_size` is zero
133 pub fn new(
134 max_cycles: Option<u32>,
135 expected_cycles: u32,
136 core_trace_fragment_size: usize,
137 ) -> Result<Self, ExecutionOptionsError> {
138 // Validate max cycles.
139 let max_cycles = if let Some(max_cycles) = max_cycles {
140 if max_cycles > Self::MAX_CYCLES {
141 return Err(ExecutionOptionsError::MaxCycleNumTooBig {
142 max_cycles,
143 max_cycles_limit: Self::MAX_CYCLES,
144 });
145 }
146 if max_cycles < MIN_TRACE_LEN as u32 {
147 return Err(ExecutionOptionsError::MaxCycleNumTooSmall {
148 max_cycles,
149 min_cycles_limit: MIN_TRACE_LEN,
150 });
151 }
152 max_cycles
153 } else {
154 Self::MAX_CYCLES
155 };
156 // Round up the expected number of cycles to the next power of two. If it is smaller than
157 // MIN_TRACE_LEN -- pad expected number to it.
158 let expected_cycles = expected_cycles.next_power_of_two().max(MIN_TRACE_LEN as u32);
159 // Validate expected cycles (after rounding) against max_cycles.
160 if max_cycles < expected_cycles {
161 return Err(ExecutionOptionsError::ExpectedCyclesTooBig {
162 max_cycles,
163 expected_cycles,
164 });
165 }
166
167 // Validate core trace fragment size.
168 if core_trace_fragment_size == 0 {
169 return Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall);
170 }
171
172 Ok(ExecutionOptions {
173 max_cycles,
174 expected_cycles,
175 core_trace_fragment_size,
176 max_adv_map_value_size: Self::DEFAULT_MAX_ADV_MAP_VALUE_SIZE,
177 max_adv_map_elements: Self::DEFAULT_MAX_ADV_MAP_ELEMENTS,
178 max_hash_len_bytes: Self::DEFAULT_MAX_HASH_LEN_BYTES,
179 max_deferred_elements: Self::DEFAULT_MAX_DEFERRED_ELEMENTS,
180 max_num_continuations: Self::DEFAULT_MAX_NUM_CONTINUATIONS,
181 max_merkle_store_nodes: Self::DEFAULT_MAX_MERKLE_STORE_NODES,
182 max_stack_depth: Self::DEFAULT_MAX_STACK_DEPTH,
183 max_memory_elements: Self::DEFAULT_MAX_MEMORY_ELEMENTS,
184 overlapped_trace_build: true,
185 })
186 }
187
188 /// Sets the fragment size for core trace generation.
189 ///
190 /// Returns an error if the size is zero.
191 pub fn with_core_trace_fragment_size(
192 mut self,
193 size: usize,
194 ) -> Result<Self, ExecutionOptionsError> {
195 if size == 0 {
196 return Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall);
197 }
198 self.core_trace_fragment_size = size;
199 Ok(self)
200 }
201
202 // PUBLIC ACCESSORS
203 // --------------------------------------------------------------------------------------------
204
205 /// Returns maximum number of cycles a program is allowed to execute for.
206 #[inline(always)]
207 pub fn max_cycles(&self) -> u32 {
208 self.max_cycles
209 }
210
211 /// Returns the number of cycles a program is expected to take.
212 ///
213 /// This will serve as a hint to the VM for how much memory to allocate for a program's
214 /// execution trace and may result in performance improvements when the number of expected
215 /// cycles is equal to the number of actual cycles.
216 pub fn expected_cycles(&self) -> u32 {
217 self.expected_cycles
218 }
219
220 /// Returns the fragment size for core trace generation.
221 pub fn core_trace_fragment_size(&self) -> usize {
222 self.core_trace_fragment_size
223 }
224
225 /// Returns the maximum number of field elements allowed in a single live advice map value.
226 #[inline]
227 pub fn max_adv_map_value_size(&self) -> usize {
228 self.max_adv_map_value_size
229 }
230
231 /// Returns the maximum total number of field elements allowed in live advice map keys and
232 /// values.
233 #[inline]
234 pub fn max_adv_map_elements(&self) -> usize {
235 self.max_adv_map_elements
236 }
237
238 /// Returns the maximum number of input bytes allowed for a single hash precompile invocation.
239 #[inline]
240 pub fn max_hash_len_bytes(&self) -> usize {
241 self.max_hash_len_bytes
242 }
243
244 /// Returns the maximum approximate number of field elements allowed in deferred state.
245 #[inline]
246 pub fn max_deferred_elements(&self) -> usize {
247 self.max_deferred_elements
248 }
249
250 /// Selects the overlapped `prove_sync` route (defaults to `true`). Ignored on no_std, which
251 /// never reads it. That route still builds the trace sequentially where the builder thread
252 /// cannot be spawned.
253 pub fn with_overlapped_trace_build(mut self, overlapped: bool) -> Self {
254 self.overlapped_trace_build = overlapped;
255 self
256 }
257
258 /// Returns whether the synchronous prover is asked to overlap trace building with execution.
259 ///
260 /// This reports the request, not the outcome: a target that cannot spawn a thread builds the
261 /// trace sequentially even when this returns `true`.
262 pub fn overlapped_trace_build(&self) -> bool {
263 self.overlapped_trace_build
264 }
265
266 /// Sets the maximum number of field elements allowed in a single live advice map value.
267 pub fn with_max_adv_map_value_size(mut self, size: usize) -> Self {
268 self.max_adv_map_value_size = size;
269 self
270 }
271
272 /// Sets the maximum total number of field elements allowed in live advice map keys and values.
273 pub fn with_max_adv_map_elements(mut self, size: usize) -> Self {
274 self.max_adv_map_elements = size;
275 self
276 }
277
278 /// Sets the maximum number of input bytes allowed for a single hash precompile invocation.
279 pub fn with_max_hash_len_bytes(mut self, size: usize) -> Self {
280 self.max_hash_len_bytes = size;
281 self
282 }
283
284 /// Sets the maximum approximate number of field elements allowed in deferred state.
285 pub fn with_max_deferred_elements(mut self, size: usize) -> Self {
286 self.max_deferred_elements = size;
287 self
288 }
289
290 /// Returns the maximum number of continuations allowed on the continuation stack.
291 #[inline]
292 pub fn max_num_continuations(&self) -> usize {
293 self.max_num_continuations
294 }
295
296 /// Returns the maximum number of internal nodes allowed in the advice provider's Merkle store.
297 #[inline]
298 pub fn max_merkle_store_nodes(&self) -> usize {
299 self.max_merkle_store_nodes
300 }
301
302 /// Returns the maximum number of field elements allowed on the operand stack across the active
303 /// execution context and all suspended contexts.
304 #[inline]
305 pub fn max_stack_depth(&self) -> usize {
306 self.max_stack_depth
307 }
308
309 /// Returns the configured maximum number of field elements allowed in the processor's memory.
310 ///
311 /// This is the raw value as set via [`Self::with_max_memory_elements`]; the effective cap is
312 /// rounded up to a whole number of words (a multiple of 4) when memory is initialized.
313 #[inline]
314 pub fn max_memory_elements(&self) -> usize {
315 self.max_memory_elements
316 }
317
318 /// Sets the maximum number of continuations allowed on the continuation stack.
319 pub fn with_max_num_continuations(mut self, max_num_continuations: usize) -> Self {
320 self.max_num_continuations = max_num_continuations;
321 self
322 }
323
324 /// Sets the maximum number of internal nodes allowed in the advice provider's Merkle store.
325 pub fn with_max_merkle_store_nodes(mut self, max_merkle_store_nodes: usize) -> Self {
326 self.max_merkle_store_nodes = max_merkle_store_nodes;
327 self
328 }
329
330 /// Sets the maximum number of field elements allowed on the operand stack across the active
331 /// execution context and all suspended contexts.
332 pub fn with_max_stack_depth(
333 mut self,
334 max_stack_depth: usize,
335 ) -> Result<Self, ExecutionOptionsError> {
336 if max_stack_depth < MIN_STACK_DEPTH {
337 return Err(ExecutionOptionsError::MaxStackDepthTooSmall {
338 max_stack_depth,
339 min_stack_depth: MIN_STACK_DEPTH,
340 });
341 }
342 self.max_stack_depth = max_stack_depth;
343 Ok(self)
344 }
345
346 /// Sets the maximum number of field elements allowed in the processor's memory.
347 pub fn with_max_memory_elements(mut self, max_memory_elements: usize) -> Self {
348 self.max_memory_elements = max_memory_elements;
349 self
350 }
351}
352
353// EXECUTION OPTIONS ERROR
354// ================================================================================================
355
356#[derive(Debug, thiserror::Error)]
357pub enum ExecutionOptionsError {
358 #[error(
359 "expected number of cycles {expected_cycles} must be smaller than the maximum number of cycles {max_cycles}"
360 )]
361 ExpectedCyclesTooBig { max_cycles: u32, expected_cycles: u32 },
362 #[error("maximum number of cycles {max_cycles} must be greater than {min_cycles_limit}")]
363 MaxCycleNumTooSmall { max_cycles: u32, min_cycles_limit: usize },
364 #[error("maximum number of cycles {max_cycles} must be less than {max_cycles_limit}")]
365 MaxCycleNumTooBig { max_cycles: u32, max_cycles_limit: u32 },
366 #[error("core trace fragment size must be greater than 0")]
367 CoreTraceFragmentSizeTooSmall,
368 #[error("maximum stack depth {max_stack_depth} must be at least {min_stack_depth}")]
369 MaxStackDepthTooSmall {
370 max_stack_depth: usize,
371 min_stack_depth: usize,
372 },
373}
374
375// TESTS
376// ================================================================================================
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn valid_fragment_size() {
384 // Valid power of two values should succeed
385 let opts = ExecutionOptions::new(None, 64, 1024);
386 assert!(opts.is_ok());
387 assert_eq!(opts.unwrap().core_trace_fragment_size(), 1024);
388
389 let opts = ExecutionOptions::new(None, 64, 4096);
390 assert!(opts.is_ok());
391
392 let opts = ExecutionOptions::new(None, 64, 1);
393 assert!(opts.is_ok());
394 }
395
396 #[test]
397 fn zero_fragment_size_fails() {
398 let opts = ExecutionOptions::new(None, 64, 0);
399 assert!(matches!(opts, Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall)));
400 }
401
402 #[test]
403 fn with_core_trace_fragment_size_validates() {
404 // Valid size should succeed
405 let result = ExecutionOptions::default().with_core_trace_fragment_size(2048);
406 assert!(result.is_ok());
407 assert_eq!(result.unwrap().core_trace_fragment_size(), 2048);
408
409 // Zero should fail
410 let result = ExecutionOptions::default().with_core_trace_fragment_size(0);
411 assert!(matches!(result, Err(ExecutionOptionsError::CoreTraceFragmentSizeTooSmall)));
412 }
413
414 #[test]
415 fn expected_cycles_validated_after_rounding() {
416 // expected_cycles=65 rounds to 128; max_cycles=100 -> must fail (128 > 100).
417 let opts = ExecutionOptions::new(Some(100), 65, 1024);
418 assert!(matches!(
419 opts,
420 Err(ExecutionOptionsError::ExpectedCyclesTooBig {
421 max_cycles: 100,
422 expected_cycles: 128
423 })
424 ));
425
426 // expected_cycles=64 rounds to 64; max_cycles=100 -> ok.
427 let opts = ExecutionOptions::new(Some(100), 64, 1024);
428 assert!(opts.is_ok());
429 assert_eq!(opts.unwrap().expected_cycles(), 64);
430 }
431
432 #[test]
433 fn max_stack_depth_validates_minimum_depth() {
434 let result = ExecutionOptions::default().with_max_stack_depth(MIN_STACK_DEPTH - 1);
435 assert!(matches!(
436 result,
437 Err(ExecutionOptionsError::MaxStackDepthTooSmall {
438 max_stack_depth,
439 min_stack_depth: MIN_STACK_DEPTH,
440 }) if max_stack_depth == MIN_STACK_DEPTH - 1
441 ));
442
443 let result = ExecutionOptions::default().with_max_stack_depth(MIN_STACK_DEPTH);
444 assert!(result.is_ok());
445 assert_eq!(result.unwrap().max_stack_depth(), MIN_STACK_DEPTH);
446 }
447}