Skip to main content

tasm_lib/arithmetic/xfe/
to_the_power_of_power_of_2.rs

1use std::collections::HashMap;
2
3use triton_vm::prelude::*;
4
5use crate::prelude::*;
6use crate::traits::basic_snippet::Reviewer;
7use crate::traits::basic_snippet::SignOffFingerprint;
8
9/// ### Behavior
10///
11/// ```text
12/// BEFORE: _ [exp: u32] [base: XFieldElement]
13/// AFTER:  _ [base^(2^exp): XFieldElement]
14/// ```
15///
16/// ### Preconditions
17///
18/// - all input arguments are properly [`BFieldCodec`] encoded
19///
20/// ### Postconditions
21///
22/// None.
23//
24// todo: Explain the precondition. It seems to be a bit arbitrary. The tests
25//   only check `exp` in range 0..32, while the assembly should work fine with
26//   any number.
27#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
28pub struct ToThePowerOfPowerOf2;
29
30impl BasicSnippet for ToThePowerOfPowerOf2 {
31    fn parameters(&self) -> Vec<(DataType, String)> {
32        vec![
33            (DataType::U32, "log_2_exponent".to_owned()),
34            (DataType::Xfe, "base".to_owned()),
35        ]
36    }
37
38    fn return_values(&self) -> Vec<(DataType, String)> {
39        vec![(DataType::Xfe, "result".to_owned())]
40    }
41
42    fn entrypoint(&self) -> String {
43        "tasmlib_arithmetic_xfe_to_the_power_of_power_of_2".to_owned()
44    }
45
46    fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
47        let entrypoint = self.entrypoint();
48        let loop_label = format!("{entrypoint}_loop");
49
50        triton_asm!(
51            {entrypoint}:
52                call {loop_label}
53
54                pick 3
55                pop 1
56
57                return
58
59            // INVARIANT: _ remainder [acc]
60            {loop_label}:
61                // end condition: remainder == 0
62                dup 3
63                push 0
64                eq
65                skiz
66                    return
67
68                // _ remainder [acc]
69                dup 2
70                dup 2
71                dup 2
72                xx_mul
73                // _ remainder [acc^2]
74
75                pick 3
76                addi -1
77                place 3
78                // _ (remainder - 1) [acc^2]
79
80                recurse
81        )
82    }
83
84    fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
85        let mut sign_offs = HashMap::new();
86        sign_offs.insert(Reviewer("ferdinand"), 0xd05170d4c435dfad.into());
87        sign_offs
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use twenty_first::math::traits::ModPowU32;
94
95    use super::*;
96    use crate::arithmetic::xfe::mod_pow_u32::XfeModPowU32;
97    use crate::test_prelude::*;
98
99    impl Closure for ToThePowerOfPowerOf2 {
100        type Args = (u32, XFieldElement);
101
102        fn rust_shadow(&self, stack: &mut Vec<BFieldElement>) -> Result<(), RustShadowError> {
103            let (exponent_log_2, base) = pop_encodable::<Self::Args>(stack)?;
104            let result = base.mod_pow_u32(2_u32.pow(exponent_log_2));
105            push_encodable(stack, &result);
106            Ok(())
107        }
108
109        fn pseudorandom_args(
110            &self,
111            seed: [u8; 32],
112            bench_case: Option<BenchmarkCase>,
113        ) -> Self::Args {
114            let mut rng = StdRng::from_seed(seed);
115            let exponent = match bench_case {
116                Some(BenchmarkCase::CommonCase) => 20,
117                Some(BenchmarkCase::WorstCase) => 31,
118                None => rng.random_range(0..32),
119            };
120
121            (exponent, rng.random())
122        }
123
124        fn corner_case_args(&self) -> Vec<Self::Args> {
125            (0..=5)
126                .chain([30, 31])
127                .map(|log_2_exp| (log_2_exp, xfe!([14; 3])))
128                .collect()
129        }
130    }
131
132    #[macro_rules_attr::apply(test)]
133    fn rust_shadow() {
134        ShadowedClosure::new(ToThePowerOfPowerOf2).test();
135    }
136
137    #[macro_rules_attr::apply(test)]
138    fn compare_to_generic_pow_u32() {
139        let base = rand::random();
140        for log_2_exponent in 0..32 {
141            let final_pow_pow_stack = test_rust_equivalence_given_complete_state(
142                &ShadowedClosure::new(ToThePowerOfPowerOf2),
143                &ToThePowerOfPowerOf2.set_up_test_stack((log_2_exponent, base)),
144                &[],
145                &NonDeterminism::default(),
146                &None,
147                None,
148            )
149            .op_stack
150            .stack;
151
152            let final_generic_stack = test_rust_equivalence_given_complete_state(
153                &ShadowedClosure::new(XfeModPowU32),
154                &XfeModPowU32.set_up_test_stack((2_u32.pow(log_2_exponent), base)),
155                &[],
156                &NonDeterminism::default(),
157                &None,
158                None,
159            )
160            .op_stack
161            .stack;
162
163            // Assert that height agrees, and the top-3 elements agree
164            assert_eq!(19, final_pow_pow_stack.len());
165            assert_eq!(19, final_generic_stack.len());
166            assert_eq!(final_pow_pow_stack[16..=18], final_generic_stack[16..=18],);
167        }
168    }
169}
170
171#[cfg(test)]
172mod benches {
173    use super::*;
174    use crate::test_prelude::*;
175
176    #[macro_rules_attr::apply(test)]
177    fn benchmark() {
178        ShadowedClosure::new(ToThePowerOfPowerOf2).bench();
179    }
180}