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 inputs(&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 outputs(&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!("{}_loop", entrypoint);
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"), 0x7768250498bfbb26.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>) {
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        }
107
108        fn pseudorandom_args(
109            &self,
110            seed: [u8; 32],
111            bench_case: Option<BenchmarkCase>,
112        ) -> Self::Args {
113            let mut rng = StdRng::from_seed(seed);
114            let exponent = match bench_case {
115                Some(BenchmarkCase::CommonCase) => 20,
116                Some(BenchmarkCase::WorstCase) => 31,
117                None => rng.random_range(0..32),
118            };
119
120            (exponent, rng.random())
121        }
122
123        fn corner_case_args(&self) -> Vec<Self::Args> {
124            (0..=5)
125                .chain([30, 31])
126                .map(|log_2_exp| (log_2_exp, xfe!([14; 3])))
127                .collect()
128        }
129    }
130
131    #[test]
132    fn rust_shadow() {
133        ShadowedClosure::new(ToThePowerOfPowerOf2).test()
134    }
135
136    #[test]
137    fn compare_to_generic_pow_u32() {
138        let base = rand::random();
139        for log_2_exponent in 0..32 {
140            let final_pow_pow_stack = test_rust_equivalence_given_complete_state(
141                &ShadowedClosure::new(ToThePowerOfPowerOf2),
142                &ToThePowerOfPowerOf2.set_up_test_stack((log_2_exponent, base)),
143                &[],
144                &NonDeterminism::default(),
145                &None,
146                None,
147            )
148            .op_stack
149            .stack;
150
151            let final_generic_stack = test_rust_equivalence_given_complete_state(
152                &ShadowedClosure::new(XfeModPowU32),
153                &XfeModPowU32.set_up_test_stack((2_u32.pow(log_2_exponent), base)),
154                &[],
155                &NonDeterminism::default(),
156                &None,
157                None,
158            )
159            .op_stack
160            .stack;
161
162            // Assert that height agrees, and the top-3 elements agree
163            assert_eq!(19, final_pow_pow_stack.len());
164            assert_eq!(19, final_generic_stack.len());
165            assert_eq!(final_pow_pow_stack[16..=18], final_generic_stack[16..=18],);
166        }
167    }
168}
169
170#[cfg(test)]
171mod benches {
172    use super::*;
173    use crate::test_prelude::*;
174
175    #[test]
176    fn benchmark() {
177        ShadowedClosure::new(ToThePowerOfPowerOf2).bench();
178    }
179}