Skip to main content

miden_processor/fast/
operation.rs

1use miden_air::{
2    Felt, FieldElement, RowIndex,
3    trace::{chiplets::hasher::HasherState, decoder::NUM_USER_OP_HELPERS},
4};
5use miden_core::{
6    QuadFelt, WORD_SIZE, Word, ZERO,
7    crypto::{hash::Rpo256, merkle::MerklePath},
8    precompile::{PrecompileTranscript, PrecompileTranscriptState},
9};
10
11use crate::{
12    AdviceProvider, ContextId, ErrorContext, ExecutionError, ProcessState,
13    chiplets::{CircuitEvaluation, MAX_NUM_ACE_WIRES, PTR_OFFSET_ELEM, PTR_OFFSET_WORD},
14    errors::AceError,
15    fast::{FastProcessor, STACK_BUFFER_SIZE, Tracer, memory::Memory},
16    processor::{
17        HasherInterface, MemoryInterface, OperationHelperRegisters, Processor, StackInterface,
18        SystemInterface,
19    },
20};
21
22impl Processor for FastProcessor {
23    type HelperRegisters = NoopHelperRegisters;
24    type System = Self;
25    type Stack = Self;
26    type AdviceProvider = AdviceProvider;
27    type Memory = Memory;
28    type Hasher = Self;
29
30    #[inline(always)]
31    fn stack(&mut self) -> &mut Self::Stack {
32        self
33    }
34
35    #[inline(always)]
36    fn state(&mut self) -> ProcessState<'_> {
37        self.state()
38    }
39
40    #[inline(always)]
41    fn advice_provider(&mut self) -> &mut Self::AdviceProvider {
42        &mut self.advice
43    }
44
45    #[inline(always)]
46    fn memory(&mut self) -> &mut Self::Memory {
47        &mut self.memory
48    }
49
50    #[inline(always)]
51    fn hasher(&mut self) -> &mut Self::Hasher {
52        self
53    }
54
55    #[inline(always)]
56    fn system(&mut self) -> &mut Self::System {
57        self
58    }
59
60    #[inline(always)]
61    fn precompile_transcript_state(&self) -> PrecompileTranscriptState {
62        self.pc_transcript.state()
63    }
64
65    #[inline(always)]
66    fn set_precompile_transcript_state(&mut self, state: PrecompileTranscriptState) {
67        self.pc_transcript = PrecompileTranscript::from_state(state);
68    }
69
70    /// Checks that the evaluation of an arithmetic circuit is equal to zero.
71    ///
72    /// The inputs are composed of:
73    ///
74    /// 1. a pointer to the memory region containing the arithmetic circuit description, which
75    ///    itself is arranged as:
76    ///
77    ///    a. `Read` section:
78    ///       1. Inputs to the circuit which are elements in the quadratic extension field,
79    ///       2. Constants of the circuit which are elements in the quadratic extension field,
80    ///
81    ///    b. `Eval` section, which contains the encodings of the evaluation gates of the circuit,
82    ///    where each gate is encoded as a single base field element.
83    /// 2. the number of quadratic extension field elements read in the `READ` section,
84    /// 3. the number of field elements, one base field element per gate, in the `EVAL` section,
85    ///
86    /// Stack transition:
87    /// [ptr, num_read, num_eval, ...] -> [ptr, num_read, num_eval, ...]
88    ///
89    /// Note that we do not record any memory reads in this operation (through a
90    /// [crate::fast::Tracer]), because the parallel trace generation skips the circuit
91    /// evaluation completely.
92    fn op_eval_circuit(
93        &mut self,
94        err_ctx: &impl ErrorContext,
95        tracer: &mut impl Tracer,
96    ) -> Result<(), ExecutionError> {
97        let num_eval = self.stack_get(2);
98        let num_read = self.stack_get(1);
99        let ptr = self.stack_get(0);
100        let ctx = self.ctx;
101        let circuit_evaluation = eval_circuit_fast_(
102            ctx,
103            ptr,
104            self.clk,
105            num_read,
106            num_eval,
107            &mut self.memory,
108            err_ctx,
109            tracer,
110        )?;
111        self.ace.add_circuit_evaluation(self.clk, circuit_evaluation.clone());
112        tracer.record_circuit_evaluation(self.clk, circuit_evaluation);
113
114        Ok(())
115    }
116}
117
118impl HasherInterface for FastProcessor {
119    #[inline(always)]
120    fn permute(&mut self, mut input_state: HasherState) -> (Felt, HasherState) {
121        Rpo256::apply_permutation(&mut input_state);
122
123        // Return a default value for the address, as it is not needed in trace generation.
124        (ZERO, input_state)
125    }
126
127    #[inline(always)]
128    fn verify_merkle_root(
129        &mut self,
130        claimed_root: Word,
131        value: Word,
132        path: Option<&MerklePath>,
133        index: Felt,
134        on_err: impl FnOnce() -> ExecutionError,
135    ) -> Result<Felt, ExecutionError> {
136        let path = path.expect("fast processor expects a valid Merkle path");
137        match path.verify(index.as_int(), value, &claimed_root) {
138            // Return a default value for the address, as it is not needed in trace generation.
139            Ok(_) => Ok(ZERO),
140            Err(_) => Err(on_err()),
141        }
142    }
143
144    #[inline(always)]
145    fn update_merkle_root(
146        &mut self,
147        claimed_old_root: Word,
148        old_value: Word,
149        new_value: Word,
150        path: Option<&MerklePath>,
151        index: Felt,
152        on_err: impl FnOnce() -> ExecutionError,
153    ) -> Result<(Felt, Word), ExecutionError> {
154        let path = path.expect("fast processor expects a valid Merkle path");
155
156        // Verify the old value against the claimed old root.
157        if path.verify(index.as_int(), old_value, &claimed_old_root).is_err() {
158            return Err(on_err());
159        };
160
161        // Compute the new root.
162        let new_root = path.compute_root(index.as_int(), new_value).map_err(|_| on_err())?;
163
164        Ok((ZERO, new_root))
165    }
166}
167
168impl SystemInterface for FastProcessor {
169    #[inline(always)]
170    fn caller_hash(&self) -> Word {
171        self.caller_hash
172    }
173
174    #[inline(always)]
175    fn clk(&self) -> RowIndex {
176        self.clk
177    }
178
179    #[inline(always)]
180    fn ctx(&self) -> ContextId {
181        self.ctx
182    }
183}
184
185impl StackInterface for FastProcessor {
186    #[inline(always)]
187    fn top(&self) -> &[Felt] {
188        self.stack_top()
189    }
190
191    #[inline(always)]
192    fn top_mut(&mut self) -> &mut [Felt] {
193        self.stack_top_mut()
194    }
195
196    #[inline(always)]
197    fn get(&self, idx: usize) -> Felt {
198        self.stack_get(idx)
199    }
200
201    #[inline(always)]
202    fn get_mut(&mut self, idx: usize) -> &mut Felt {
203        self.stack_get_mut(idx)
204    }
205
206    #[inline(always)]
207    fn get_word(&self, start_idx: usize) -> Word {
208        self.stack_get_word(start_idx)
209    }
210
211    #[inline(always)]
212    fn depth(&self) -> u32 {
213        self.stack_depth()
214    }
215
216    #[inline(always)]
217    fn set(&mut self, idx: usize, element: Felt) {
218        self.stack_write(idx, element)
219    }
220
221    #[inline(always)]
222    fn set_word(&mut self, start_idx: usize, word: &Word) {
223        self.stack_write_word(start_idx, word);
224    }
225
226    #[inline(always)]
227    fn swap(&mut self, idx1: usize, idx2: usize) {
228        self.stack_swap(idx1, idx2)
229    }
230
231    #[inline(always)]
232    fn swapw_nth(&mut self, n: usize) {
233        // For example, for n=3, the stack words and variables look like:
234        //    3     2     1     0
235        // | ... | ... | ... | ... |
236        // ^                 ^
237        // nth_word       top_word
238        let (rest_of_stack, top_word) = self.stack.split_at_mut(self.stack_top_idx - WORD_SIZE);
239        let (_, nth_word) = rest_of_stack.split_at_mut(rest_of_stack.len() - n * WORD_SIZE);
240
241        nth_word[0..WORD_SIZE].swap_with_slice(&mut top_word[0..WORD_SIZE]);
242    }
243
244    #[inline(always)]
245    fn rotate_left(&mut self, n: usize) {
246        let rotation_bot_index = self.stack_top_idx - n;
247        let new_stack_top_element = self.stack[rotation_bot_index];
248
249        // shift the top n elements down by 1, starting from the bottom of the rotation.
250        for i in 0..n - 1 {
251            self.stack[rotation_bot_index + i] = self.stack[rotation_bot_index + i + 1];
252        }
253
254        // Set the top element (which comes from the bottom of the rotation).
255        self.stack_write(0, new_stack_top_element);
256    }
257
258    #[inline(always)]
259    fn rotate_right(&mut self, n: usize) {
260        let rotation_bot_index = self.stack_top_idx - n;
261        let new_stack_bot_element = self.stack[self.stack_top_idx - 1];
262
263        // shift the top n elements up by 1, starting from the top of the rotation.
264        for i in 1..n {
265            self.stack[self.stack_top_idx - i] = self.stack[self.stack_top_idx - i - 1];
266        }
267
268        // Set the bot element (which comes from the top of the rotation).
269        self.stack[rotation_bot_index] = new_stack_bot_element;
270    }
271
272    #[inline(always)]
273    fn increment_size(&mut self, tracer: &mut impl Tracer) -> Result<(), ExecutionError> {
274        if self.stack_top_idx < STACK_BUFFER_SIZE - 1 {
275            self.increment_stack_size(tracer);
276            Ok(())
277        } else {
278            Err(ExecutionError::FailedToExecuteProgram("stack overflow"))
279        }
280    }
281
282    #[inline(always)]
283    fn decrement_size(&mut self, tracer: &mut impl Tracer) {
284        self.decrement_stack_size(tracer)
285    }
286}
287
288pub struct NoopHelperRegisters;
289
290/// Dummy helpers implementation used in the fast processor, where we don't compute the helper
291/// registers. These are expected to be ignored.
292const DEFAULT_HELPERS: [Felt; NUM_USER_OP_HELPERS] = [ZERO; NUM_USER_OP_HELPERS];
293
294impl OperationHelperRegisters for NoopHelperRegisters {
295    #[inline(always)]
296    fn op_eq_registers(_a: Felt, _b: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
297        DEFAULT_HELPERS
298    }
299
300    #[inline(always)]
301    fn op_u32split_registers(_hi: Felt, _lo: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
302        DEFAULT_HELPERS
303    }
304
305    #[inline(always)]
306    fn op_eqz_registers(_top: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
307        DEFAULT_HELPERS
308    }
309
310    #[inline(always)]
311    fn op_expacc_registers(_acc_update_val: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
312        DEFAULT_HELPERS
313    }
314
315    #[inline(always)]
316    fn op_fri_ext2fold4_registers(
317        _ev: QuadFelt,
318        _es: QuadFelt,
319        _x: Felt,
320        _x_inv: Felt,
321    ) -> [Felt; NUM_USER_OP_HELPERS] {
322        DEFAULT_HELPERS
323    }
324
325    #[inline(always)]
326    fn op_u32add_registers(_hi: Felt, _lo: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
327        DEFAULT_HELPERS
328    }
329
330    #[inline(always)]
331    fn op_u32add3_registers(_hi: Felt, _lo: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
332        DEFAULT_HELPERS
333    }
334
335    #[inline(always)]
336    fn op_u32sub_registers(_second_new: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
337        DEFAULT_HELPERS
338    }
339
340    #[inline(always)]
341    fn op_u32mul_registers(_hi: Felt, _lo: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
342        DEFAULT_HELPERS
343    }
344
345    #[inline(always)]
346    fn op_u32madd_registers(_hi: Felt, _lo: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
347        DEFAULT_HELPERS
348    }
349
350    #[inline(always)]
351    fn op_u32div_registers(_hi: Felt, _lo: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
352        DEFAULT_HELPERS
353    }
354
355    #[inline(always)]
356    fn op_u32assert2_registers(_first: Felt, _second: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
357        DEFAULT_HELPERS
358    }
359
360    #[inline(always)]
361    fn op_hperm_registers(_addr: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
362        DEFAULT_HELPERS
363    }
364
365    #[inline(always)]
366    fn op_log_precompile_registers(_addr: Felt, _cap_prev: Word) -> [Felt; NUM_USER_OP_HELPERS] {
367        DEFAULT_HELPERS
368    }
369
370    #[inline(always)]
371    fn op_merkle_path_registers(_addr: Felt) -> [Felt; NUM_USER_OP_HELPERS] {
372        DEFAULT_HELPERS
373    }
374
375    #[inline(always)]
376    fn op_horner_eval_base_registers(
377        _alpha: QuadFelt,
378        _tmp0: QuadFelt,
379        _tmp1: QuadFelt,
380    ) -> [Felt; NUM_USER_OP_HELPERS] {
381        DEFAULT_HELPERS
382    }
383
384    #[inline(always)]
385    fn op_horner_eval_ext_registers(
386        _alpha: QuadFelt,
387        _k0: Felt,
388        _k1: Felt,
389        _acc_tmp: QuadFelt,
390    ) -> [Felt; NUM_USER_OP_HELPERS] {
391        DEFAULT_HELPERS
392    }
393}
394
395// HELPERS
396// ================================================================================================
397
398/// Identical to `[chiplets::ace::eval_circuit]` but adapted for use with `[FastProcessor]`.
399#[expect(clippy::too_many_arguments)]
400pub fn eval_circuit_fast_(
401    ctx: ContextId,
402    ptr: Felt,
403    clk: RowIndex,
404    num_vars: Felt,
405    num_eval: Felt,
406    mem: &mut impl MemoryInterface,
407    err_ctx: &impl ErrorContext,
408    tracer: &mut impl Tracer,
409) -> Result<CircuitEvaluation, ExecutionError> {
410    let num_vars = num_vars.as_int();
411    let num_eval = num_eval.as_int();
412
413    let num_wires = num_vars + num_eval;
414    if num_wires > MAX_NUM_ACE_WIRES as u64 {
415        return Err(ExecutionError::failed_arithmetic_evaluation(
416            err_ctx,
417            AceError::TooManyWires(num_wires),
418        ));
419    }
420
421    // Ensure vars and instructions are word-aligned and non-empty. Note that variables are
422    // quadratic extension field elements while instructions are encoded as base field elements.
423    // Hence we can pack 2 variables and 4 instructions per word.
424    if !num_vars.is_multiple_of(2) || num_vars == 0 {
425        return Err(ExecutionError::failed_arithmetic_evaluation(
426            err_ctx,
427            AceError::NumVarIsNotWordAlignedOrIsEmpty(num_vars),
428        ));
429    }
430    if !num_eval.is_multiple_of(4) || num_eval == 0 {
431        return Err(ExecutionError::failed_arithmetic_evaluation(
432            err_ctx,
433            AceError::NumEvalIsNotWordAlignedOrIsEmpty(num_eval),
434        ));
435    }
436
437    // Ensure instructions are word-aligned and non-empty
438    let num_read_rows = num_vars as u32 / 2;
439    let num_eval_rows = num_eval as u32;
440
441    let mut evaluation_context = CircuitEvaluation::new(ctx, clk, num_read_rows, num_eval_rows);
442
443    let mut ptr = ptr;
444    // perform READ operations
445    // Note: we pass in a `NoopTracer`, because the parallel trace generation skips the circuit
446    // evaluation completely
447    for _ in 0..num_read_rows {
448        let word = mem.read_word(ctx, ptr, clk, err_ctx).map_err(ExecutionError::MemoryError)?;
449        tracer.record_memory_read_word(word, ptr, ctx, clk);
450        evaluation_context.do_read(ptr, word)?;
451        ptr += PTR_OFFSET_WORD;
452    }
453    // perform EVAL operations
454    for _ in 0..num_eval_rows {
455        let instruction =
456            mem.read_element(ctx, ptr, err_ctx).map_err(ExecutionError::MemoryError)?;
457        tracer.record_memory_read_element(instruction, ptr, ctx, clk);
458        evaluation_context.do_eval(ptr, instruction, err_ctx)?;
459        ptr += PTR_OFFSET_ELEM;
460    }
461
462    // Ensure the circuit evaluated to zero.
463    if !evaluation_context.output_value().is_some_and(|eval| eval == QuadFelt::ZERO) {
464        return Err(ExecutionError::failed_arithmetic_evaluation(
465            err_ctx,
466            AceError::CircuitNotEvaluateZero,
467        ));
468    }
469
470    Ok(evaluation_context)
471}