miden_processor/operations/
u32_ops.rs

1use alloc::vec::Vec;
2
3use paste::paste;
4
5use super::{
6    super::utils::{split_element, split_u32_into_u16},
7    ExecutionError, Felt, FieldElement, Operation, Process,
8};
9use crate::{ErrorContext, ZERO};
10
11const U32_MAX: u64 = u32::MAX as u64;
12
13macro_rules! require_u32_operands {
14    ($stack:expr, [$($idx:expr),*], $err_ctx:expr) => {
15        require_u32_operands!($stack, [$($idx),*], ZERO, $err_ctx)
16    };
17    ($stack:expr, [$($idx:expr),*], $errno:expr, $err_ctx:expr) => {{
18        paste!{
19            let mut invalid_values = Vec::new();
20
21            $(
22                let [<_operand_ $idx>] = $stack.get($idx);
23                if [<_operand_ $idx>].as_int() > U32_MAX {
24                    invalid_values.push([<_operand_ $idx>]);
25                }
26            )*
27
28            if !invalid_values.is_empty() {
29                return Err(ExecutionError::not_u32_values(invalid_values, $errno, $err_ctx));
30            }
31            // Return tuple of operands based on indices
32            ($([<_operand_ $idx>].as_int()),*)
33        }
34    }};
35}
36
37impl Process {
38    // CASTING OPERATIONS
39    // --------------------------------------------------------------------------------------------
40
41    /// Pops the top element off the stack, splits it into low and high 32-bit values, and pushes
42    /// these values back onto the stack.
43    pub(super) fn op_u32split(&mut self) -> Result<(), ExecutionError> {
44        let a = self.stack.get(0);
45        let (hi, lo) = split_element(a);
46
47        self.add_range_checks(Operation::U32split, lo, hi, true);
48
49        self.stack.set(0, hi);
50        self.stack.set(1, lo);
51        self.stack.shift_right(1);
52        Ok(())
53    }
54
55    /// Pops top two element off the stack, splits them into low and high 32-bit values, checks if
56    /// the high values are equal to 0; if they are, puts the original elements back onto the
57    /// stack; if they are not, returns an error.
58    pub(super) fn op_u32assert2(
59        &mut self,
60        err_code: Felt,
61        err_ctx: &impl ErrorContext,
62    ) -> Result<(), ExecutionError> {
63        let (b, a) = require_u32_operands!(self.stack, [0, 1], err_code, err_ctx);
64
65        self.add_range_checks(Operation::U32assert2(err_code), Felt::new(a), Felt::new(b), false);
66
67        self.stack.copy_state(0);
68        Ok(())
69    }
70
71    // ARITHMETIC OPERATIONS
72    // --------------------------------------------------------------------------------------------
73
74    /// Pops two elements off the stack, adds them, splits the result into low and high 32-bit
75    /// values, and pushes these values back onto the stack.
76    pub(super) fn op_u32add(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
77        let (b, a) = require_u32_operands!(self.stack, [0, 1], err_ctx);
78
79        let result = Felt::new(a + b);
80        let (hi, lo) = split_element(result);
81        self.add_range_checks(Operation::U32add, lo, hi, false);
82
83        self.stack.set(0, hi);
84        self.stack.set(1, lo);
85        self.stack.copy_state(2);
86        Ok(())
87    }
88
89    /// Pops three elements off the stack, adds them, splits the result into low and high 32-bit
90    /// values, and pushes these values back onto the stack.
91    pub(super) fn op_u32add3(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
92        let (c, b, a) = require_u32_operands!(self.stack, [0, 1, 2], err_ctx);
93        let result = Felt::new(a + b + c);
94        let (hi, lo) = split_element(result);
95
96        self.add_range_checks(Operation::U32add3, lo, hi, false);
97
98        self.stack.set(0, hi);
99        self.stack.set(1, lo);
100        self.stack.shift_left(3);
101        Ok(())
102    }
103
104    /// Pops two elements off the stack, subtracts the top element from the second element, and
105    /// pushes the result as well as a flag indicating whether there was underflow back onto the
106    /// stack.
107    pub(super) fn op_u32sub(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
108        let (b, a) = require_u32_operands!(self.stack, [0, 1], err_ctx);
109        let result = a.wrapping_sub(b);
110        let d = Felt::new(result >> 63);
111        let c = Felt::new(result & U32_MAX);
112
113        // Force this operation to consume 4 range checks, even though only `lo` is needed.
114        // This is required for making the constraints more uniform and grouping the opcodes of
115        // operations requiring range checks under a common degree-4 prefix.
116        self.add_range_checks(Operation::U32sub, c, ZERO, false);
117
118        self.stack.set(0, d);
119        self.stack.set(1, c);
120        self.stack.copy_state(2);
121        Ok(())
122    }
123
124    /// Pops two elements off the stack, multiplies them, splits the result into low and high
125    /// 32-bit values, and pushes these values back onto the stack.
126    pub(super) fn op_u32mul(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
127        let (b, a) = require_u32_operands!(self.stack, [0, 1], err_ctx);
128        let result = Felt::new(a * b);
129        let (hi, lo) = split_element(result);
130
131        self.add_range_checks(Operation::U32mul, lo, hi, true);
132
133        self.stack.set(0, hi);
134        self.stack.set(1, lo);
135        self.stack.copy_state(2);
136        Ok(())
137    }
138
139    /// Pops three elements off the stack, multiplies the first two and adds the third element to
140    /// the result, splits the result into low and high 32-bit values, and pushes these values
141    /// back onto the stack.
142    pub(super) fn op_u32madd(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
143        let (b, a, c) = require_u32_operands!(self.stack, [0, 1, 2], err_ctx);
144        let result = Felt::new(a * b + c);
145        let (hi, lo) = split_element(result);
146
147        self.add_range_checks(Operation::U32madd, lo, hi, true);
148
149        self.stack.set(0, hi);
150        self.stack.set(1, lo);
151        self.stack.shift_left(3);
152        Ok(())
153    }
154
155    /// Pops two elements off the stack, divides the second element by the top element, and pushes
156    /// the quotient and the remainder back onto the stack.
157    ///
158    /// # Errors
159    /// Returns an error if the divisor is ZERO.
160    pub(super) fn op_u32div(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
161        let (b, a) = require_u32_operands!(self.stack, [0, 1], err_ctx);
162
163        if b == 0 {
164            return Err(ExecutionError::divide_by_zero(self.system.clk(), err_ctx));
165        }
166
167        let q = a / b;
168        let r = a - q * b;
169
170        // These range checks help enforce that q <= a.
171        let lo = Felt::new(a - q);
172        // These range checks help enforce that r < b.
173        let hi = Felt::new(b - r - 1);
174        self.add_range_checks(Operation::U32div, lo, hi, false);
175
176        self.stack.set(0, Felt::new(r));
177        self.stack.set(1, Felt::new(q));
178        self.stack.copy_state(2);
179        Ok(())
180    }
181
182    // BITWISE OPERATIONS
183    // --------------------------------------------------------------------------------------------
184
185    /// Pops two elements off the stack, computes their bitwise AND, and pushes the result back
186    /// onto the stack.
187    pub(super) fn op_u32and(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
188        let (b, a) = require_u32_operands!(self.stack, [0, 1], err_ctx);
189        let result = self.chiplets.bitwise.u32and(Felt::new(a), Felt::new(b), err_ctx)?;
190
191        self.stack.set(0, result);
192        self.stack.shift_left(2);
193
194        Ok(())
195    }
196
197    /// Pops two elements off the stack, computes their bitwise XOR, and pushes the result back onto
198    /// the stack.
199    pub(super) fn op_u32xor(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
200        let (b, a) = require_u32_operands!(self.stack, [0, 1], err_ctx);
201        let result = self.chiplets.bitwise.u32xor(Felt::new(a), Felt::new(b), err_ctx)?;
202
203        self.stack.set(0, result);
204        self.stack.shift_left(2);
205
206        Ok(())
207    }
208
209    /// Adds 16-bit range checks to the RangeChecker for the high and low 16-bit limbs of two field
210    /// elements which are assumed to have 32-bit integer values. This results in 4 range checks.
211    ///
212    /// All range-checked values are added to the decoder to help with constraint evaluation. When
213    /// `check_element_validity` is specified, a fifth helper value is added to the decoder trace
214    /// with the value of `m`, which is used to enforce the following element validity constraint:
215    /// (1 - m * (2^32 - 1 - hi)) * lo = 0
216    /// `m` is set to the inverse of (2^32 - 1 - hi) to enforce that hi =/= 2^32 - 1.
217    fn add_range_checks(
218        &mut self,
219        op: Operation,
220        lo: Felt,
221        hi: Felt,
222        check_element_validity: bool,
223    ) {
224        let (t1, t0) = split_u32_into_u16(lo.as_int());
225        let (t3, t2) = split_u32_into_u16(hi.as_int());
226
227        // add lookup values to the range checker.
228        self.range.add_range_checks(self.system.clk(), &[t0, t1, t2, t3]);
229
230        // save the range check lookups to the decoder's user operation helper columns.
231        let mut helper_values =
232            [Felt::from(t0), Felt::from(t1), Felt::from(t2), Felt::from(t3), ZERO];
233
234        if check_element_validity {
235            let m = (Felt::from(u32::MAX) - hi).inv();
236            helper_values[4] = m;
237        }
238
239        self.decoder.set_user_op_helpers(op, &helper_values);
240    }
241}
242
243// TESTS
244// ================================================================================================
245
246#[cfg(test)]
247mod tests {
248    use miden_air::trace::decoder::NUM_USER_OP_HELPERS;
249    use miden_core::{mast::MastForest, stack::MIN_STACK_DEPTH};
250    use miden_utils_testing::rand::rand_value;
251
252    use super::{
253        super::{Felt, Operation},
254        Process, split_u32_into_u16,
255    };
256    use crate::{DefaultHost, ExecutionError, StackInputs, ZERO};
257
258    // CASTING OPERATIONS
259    // --------------------------------------------------------------------------------------------
260
261    #[test]
262    fn op_u32split() {
263        // --- test a random value ---------------------------------------------
264        let mut host = DefaultHost::default();
265        let program = &MastForest::default();
266
267        let a: u64 = rand_value();
268        let stack = StackInputs::try_from_ints([a]).unwrap();
269        let mut process = Process::new_dummy_with_decoder_helpers(stack);
270        let hi = a >> 32;
271        let lo = (a as u32) as u64;
272
273        process.execute_op(Operation::U32split, program, &mut host).unwrap();
274        let mut expected = [ZERO; 16];
275        expected[0] = Felt::new(hi);
276        expected[1] = Felt::new(lo);
277        assert_eq!(expected, process.stack.trace_state());
278
279        // --- test the rest of the stack is not modified -----------------------
280        let b: u64 = rand_value();
281        let stack = StackInputs::try_from_ints([a, b]).unwrap();
282        let mut process = Process::new_dummy_with_decoder_helpers(stack);
283        let hi = b >> 32;
284        let lo = (b as u32) as u64;
285
286        process.execute_op(Operation::U32split, program, &mut host).unwrap();
287        let mut expected = [ZERO; 16];
288        expected[0] = Felt::new(hi);
289        expected[1] = Felt::new(lo);
290        expected[2] = Felt::new(a);
291        assert_eq!(expected, process.stack.trace_state());
292    }
293
294    #[test]
295    fn op_u32assert2() {
296        // --- test random values ensuring other elements are still values are still intact -------
297        let mut host = DefaultHost::default();
298        let program = &MastForest::default();
299
300        let (a, b, c, d) = get_rand_values();
301        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
302        let mut process = Process::new_dummy_with_decoder_helpers(stack);
303
304        process.execute_op(Operation::U32assert2(ZERO), program, &mut host).unwrap();
305        let expected = build_expected(&[a, b, c, d]);
306        assert_eq!(expected, process.stack.trace_state());
307    }
308
309    #[test]
310    fn op_u32assert2_both_invalid() {
311        let mut host = DefaultHost::default();
312        let program = &MastForest::default();
313
314        // Both values > u32::MAX (4294967296 = 2^32, 4294967297 = 2^32 + 1)
315        let stack = StackInputs::try_from_ints([4294967297u64, 4294967296u64]).unwrap();
316        let mut process = Process::new_dummy_with_decoder_helpers(stack);
317
318        let result =
319            process.execute_op(Operation::U32assert2(Felt::from(123u32)), program, &mut host);
320        assert!(result.is_err());
321
322        if let Err(ExecutionError::NotU32Values { values, err_code, .. }) = result {
323            assert_eq!(err_code, Felt::from(123u32));
324            assert_eq!(values.len(), 2);
325            // Values are collected in stack order: stack[0] (top) first, then stack[1]
326            assert_eq!(values[0].as_int(), 4294967296u64); // stack[0] = top value
327            assert_eq!(values[1].as_int(), 4294967297u64); // stack[1] = second value
328        } else {
329            panic!("Expected NotU32Values error");
330        }
331    }
332
333    #[test]
334    fn op_u32assert2_second_invalid() {
335        let mut host = DefaultHost::default();
336        let program = &MastForest::default();
337
338        // First value valid, second invalid
339        let stack = StackInputs::try_from_ints([4294967297u64, 1000u64]).unwrap();
340        let mut process = Process::new_dummy_with_decoder_helpers(stack);
341
342        let result =
343            process.execute_op(Operation::U32assert2(Felt::from(456u32)), program, &mut host);
344        assert!(result.is_err());
345
346        if let Err(ExecutionError::NotU32Values { values, err_code, .. }) = result {
347            assert_eq!(err_code, Felt::from(456u32));
348            assert_eq!(values.len(), 1);
349            assert_eq!(values[0].as_int(), 4294967297u64);
350        } else {
351            panic!("Expected NotU32Values error");
352        }
353    }
354
355    #[test]
356    fn op_u32assert2_first_invalid() {
357        let mut host = DefaultHost::default();
358        let program = &MastForest::default();
359
360        // First value invalid, second valid
361        let stack = StackInputs::try_from_ints([2000u64, 4294967296u64]).unwrap();
362        let mut process = Process::new_dummy_with_decoder_helpers(stack);
363
364        let result =
365            process.execute_op(Operation::U32assert2(Felt::from(789u32)), program, &mut host);
366        assert!(result.is_err());
367
368        if let Err(ExecutionError::NotU32Values { values, err_code, .. }) = result {
369            assert_eq!(err_code, Felt::from(789u32));
370            assert_eq!(values.len(), 1);
371            assert_eq!(values[0].as_int(), 4294967296u64);
372        } else {
373            panic!("Expected NotU32Values error");
374        }
375    }
376
377    // ARITHMETIC OPERATIONS
378    // --------------------------------------------------------------------------------------------
379
380    #[test]
381    fn op_u32add() {
382        // --- test random values ---------------------------------------------
383        let mut host = DefaultHost::default();
384        let (a, b, c, d) = get_rand_values();
385        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
386        let mut process = Process::new_dummy_with_decoder_helpers(stack);
387        let program = &MastForest::default();
388
389        let (result, over) = a.overflowing_add(b);
390
391        process.execute_op(Operation::U32add, program, &mut host).unwrap();
392        let expected = build_expected(&[over as u32, result, c, d]);
393        assert_eq!(expected, process.stack.trace_state());
394
395        // --- test overflow --------------------------------------------------
396        let a = u32::MAX - 1;
397        let b = 2u32;
398
399        let stack = StackInputs::try_from_ints([a as u64, b as u64]).unwrap();
400        let mut process = Process::new_dummy_with_decoder_helpers(stack);
401        let (result, over) = a.overflowing_add(b);
402        let (b1, b0) = split_u32_into_u16(result.into());
403
404        process.execute_op(Operation::U32add, program, &mut host).unwrap();
405        let expected = build_expected(&[over as u32, result]);
406        assert_eq!(expected, process.stack.trace_state());
407
408        let expected_helper_registers =
409            build_expected_helper_registers(&[b0 as u32, b1 as u32, over as u32]);
410        assert_eq!(expected_helper_registers, process.decoder.get_user_op_helpers());
411    }
412
413    #[test]
414    fn op_u32add3() {
415        let mut host = DefaultHost::default();
416        let a = rand_value::<u32>() as u64;
417        let b = rand_value::<u32>() as u64;
418        let c = rand_value::<u32>() as u64;
419        let d = rand_value::<u32>() as u64;
420
421        let stack = StackInputs::try_from_ints([d, c, b, a]).unwrap();
422        let mut process = Process::new_dummy_with_decoder_helpers(stack);
423        let program = &MastForest::default();
424
425        let result = a + b + c;
426        let hi = (result >> 32) as u32;
427        let lo = result as u32;
428        assert!(hi <= 2);
429
430        process.execute_op(Operation::U32add3, program, &mut host).unwrap();
431        let expected = build_expected(&[hi, lo, d as u32]);
432        assert_eq!(expected, process.stack.trace_state());
433
434        // --- test with minimum stack depth ----------------------------------
435        let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack();
436        assert!(process.execute_op(Operation::U32add3, program, &mut host).is_ok());
437    }
438
439    #[test]
440    fn op_u32sub() {
441        // --- test random values ---------------------------------------------
442        let mut host = DefaultHost::default();
443        let program = &MastForest::default();
444
445        let (a, b, c, d) = get_rand_values();
446        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
447        let mut process = Process::new_dummy_with_decoder_helpers(stack);
448        let (result, under) = b.overflowing_sub(a);
449
450        process.execute_op(Operation::U32sub, program, &mut host).unwrap();
451        let expected = build_expected(&[under as u32, result, c, d]);
452        assert_eq!(expected, process.stack.trace_state());
453
454        // --- test underflow -------------------------------------------------
455        let a = 10u32;
456        let b = 11u32;
457
458        let stack = StackInputs::try_from_ints([a as u64, b as u64]).unwrap();
459        let mut process = Process::new_dummy_with_decoder_helpers(stack);
460        let (result, under) = a.overflowing_sub(b);
461
462        process.execute_op(Operation::U32sub, program, &mut host).unwrap();
463        let expected = build_expected(&[under as u32, result]);
464        assert_eq!(expected, process.stack.trace_state());
465    }
466
467    #[test]
468    fn op_u32mul() {
469        let mut host = DefaultHost::default();
470        let program = &MastForest::default();
471
472        let (a, b, c, d) = get_rand_values();
473        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
474        let mut process = Process::new_dummy_with_decoder_helpers(stack);
475        let result = (a as u64) * (b as u64);
476        let hi = (result >> 32) as u32;
477        let lo = result as u32;
478
479        process.execute_op(Operation::U32mul, program, &mut host).unwrap();
480        let expected = build_expected(&[hi, lo, c, d]);
481        assert_eq!(expected, process.stack.trace_state());
482    }
483
484    #[test]
485    fn op_u32madd() {
486        let mut host = DefaultHost::default();
487        let program = &MastForest::default();
488
489        let (a, b, c, d) = get_rand_values();
490        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
491        let mut process = Process::new_dummy_with_decoder_helpers(stack);
492        let result = (a as u64) * (b as u64) + (c as u64);
493        let hi = (result >> 32) as u32;
494        let lo = result as u32;
495
496        process.execute_op(Operation::U32madd, program, &mut host).unwrap();
497        let expected = build_expected(&[hi, lo, d]);
498        assert_eq!(expected, process.stack.trace_state());
499
500        // --- test with minimum stack depth ----------------------------------
501        let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack();
502        assert!(process.execute_op(Operation::U32madd, program, &mut host).is_ok());
503    }
504
505    #[test]
506    fn op_u32div() {
507        let mut host = DefaultHost::default();
508        let program = &MastForest::default();
509
510        let (a, b, c, d) = get_rand_values();
511        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
512        let mut process = Process::new_dummy_with_decoder_helpers(stack);
513        let q = b / a;
514        let r = b % a;
515
516        process.execute_op(Operation::U32div, program, &mut host).unwrap();
517        let expected = build_expected(&[r, q, c, d]);
518        assert_eq!(expected, process.stack.trace_state());
519    }
520
521    // BITWISE OPERATIONS
522    // --------------------------------------------------------------------------------------------
523
524    #[test]
525    fn op_u32and() {
526        let mut host = DefaultHost::default();
527        let (a, b, c, d) = get_rand_values();
528        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
529        let mut process = Process::new_dummy_with_decoder_helpers(stack);
530        let program = &MastForest::default();
531
532        process.execute_op(Operation::U32and, program, &mut host).unwrap();
533        let expected = build_expected(&[a & b, c, d]);
534        assert_eq!(expected, process.stack.trace_state());
535
536        // --- test with minimum stack depth ----------------------------------
537        let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack();
538        assert!(process.execute_op(Operation::U32and, program, &mut host).is_ok());
539    }
540
541    #[test]
542    fn op_u32xor() {
543        let mut host = DefaultHost::default();
544        let (a, b, c, d) = get_rand_values();
545        let stack = StackInputs::try_from_ints([d as u64, c as u64, b as u64, a as u64]).unwrap();
546        let mut process = Process::new_dummy_with_decoder_helpers(stack);
547        let program = &MastForest::default();
548
549        process.execute_op(Operation::U32xor, program, &mut host).unwrap();
550        let expected = build_expected(&[a ^ b, c, d]);
551        assert_eq!(expected, process.stack.trace_state());
552
553        // --- test with minimum stack depth ----------------------------------
554        let mut process = Process::new_dummy_with_decoder_helpers_and_empty_stack();
555        assert!(process.execute_op(Operation::U32xor, program, &mut host).is_ok());
556    }
557
558    // HELPER FUNCTIONS
559    // --------------------------------------------------------------------------------------------
560
561    fn get_rand_values() -> (u32, u32, u32, u32) {
562        let a = rand_value::<u64>() as u32;
563        let b = rand_value::<u64>() as u32;
564        let c = rand_value::<u64>() as u32;
565        let d = rand_value::<u64>() as u32;
566        (d, c, b, a)
567    }
568
569    fn build_expected(values: &[u32]) -> [Felt; MIN_STACK_DEPTH] {
570        let mut expected = [ZERO; MIN_STACK_DEPTH];
571        for (&value, result) in values.iter().zip(expected.iter_mut()) {
572            *result = Felt::new(value as u64);
573        }
574        expected
575    }
576
577    fn build_expected_helper_registers(values: &[u32]) -> [Felt; NUM_USER_OP_HELPERS] {
578        let mut expected = [ZERO; NUM_USER_OP_HELPERS];
579        for (&value, result) in values.iter().zip(expected.iter_mut()) {
580            *result = Felt::new(value as u64);
581        }
582        expected
583    }
584}