tasm_lib/list/
horner_evaluation_dynamic_length.rs

1use itertools::Itertools;
2use triton_vm::prelude::*;
3
4use crate::list::length::Length;
5use crate::prelude::*;
6
7/// HornerEvaluationDynamicLength takes a list of XFieldElements, representing
8/// the coefficients of a polynomial, and evaluates it in a given indeterminate,
9/// which is also an XFieldElement.
10#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
11pub struct HornerEvaluationDynamicLength;
12
13impl BasicSnippet for HornerEvaluationDynamicLength {
14    fn inputs(&self) -> Vec<(DataType, String)> {
15        vec![
16            (
17                DataType::List(Box::new(DataType::Xfe)),
18                "*coefficients".to_string(),
19            ),
20            (DataType::Xfe, "indeterminate".to_string()),
21        ]
22    }
23
24    fn outputs(&self) -> Vec<(DataType, String)> {
25        vec![(DataType::Xfe, "value".to_string())]
26    }
27
28    fn entrypoint(&self) -> String {
29        "tasmlib_list_horner_evaluation_dynamic_length".to_string()
30    }
31
32    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
33        let horner_iteration = triton_asm! {
34            // BEFORE: _ *ptr [x] [acc]
35            // AFTER:  _ *ptr-3 [x] [acc*x+ct]
36            dup 5 dup 5 dup 5    // _ *ptr [x] [acc] [x]
37            xx_mul               // _ *ptr [x] [x*acc]
38            dup 6                // _ *ptr [x] [x*acc] *ptr
39            read_mem 3           // _ *ptr [x] [x*acc] [ct] *ptr-3
40            swap 10 pop 1        // _ *ptr-3 [x] [x*acc] [ct]
41            xx_add               // _ *ptr-3 [x] [x*acc+ct]
42
43        };
44        let batch_size = 16;
45        let three_times_batch_size_plus_two = batch_size * 3 + 2;
46        let many_horner_iterations = (0..batch_size)
47            .flat_map(|_| horner_iteration.clone())
48            .collect_vec();
49
50        let entrypoint = self.entrypoint();
51        let loop_batches = format!("{entrypoint}_loop_batches");
52        let loop_remainder = format!("{entrypoint}_loop_remainder");
53        let length_of_list = library.import(Box::new(Length));
54
55        triton_asm! {
56            // BEFORE: *coefficients [x]
57            // AFTER: [poly(x)]
58            {entrypoint}:
59                dup 3                           // _ *coefficients [x] *coefficients
60                call {length_of_list}           // _ *coefficients [x] num_coefficients
61                push 3 mul                      // _ *coefficients [x] size
62                dup 4 add                       // _ *coefficients [x] *last_coefficient+2
63                dup 3 dup 3 dup 3               // _ *coefficients [x] *last_coefficient+2 [x]
64                push 0 push 0 push 0            // _ *coefficients [x] *last_coefficient+2 [x] [0]
65                call {loop_batches}
66                call {loop_remainder}           // _ *coefficients [x] *coefficients-1 [x] [poly(x)]
67                                                // _ *coefficients x2 x1 x0 *coefficients-1 x2 x1 x0 v2 v1 v0
68                swap 8 pop 1                    // _ *coefficients x2 v0 x0 *coefficients-1 x2 x1 x0 v2 v1
69                swap 8 pop 1                    // _ *coefficients v1 v0 x0 *coefficients-1 x2 x1 x0 v2
70                swap 8 pop 5 pop 1              // _ v2 v1 v0
71                return
72
73            // INVARIANT: *start [x] *ptr [x] [acc]
74            {loop_batches}:
75                dup 6       // *start [x] *ptr [x] [acc] *ptr
76                dup 11      // *start [x] *ptr [x] [acc] *ptr *start
77                push {three_times_batch_size_plus_two} add
78                            // *start [x] *ptr [x] [acc] *ptr *start+3batch+2
79                push -1 mul add
80                            // *start [x] *ptr [x] [acc] *ptr-2-*start-3batch
81                split pop 1 // *start [x] *ptr [x] [acc] hi
82                skiz return
83                {&many_horner_iterations}
84                recurse
85
86            // INVARIANT: *start [x] *ptr [x] [acc]
87            {loop_remainder}:
88                dup 6       // *start [x] *ptr [x] [acc] *ptr
89                dup 11      // *start [x] *ptr [x] [acc] *ptr *start
90                eq          // *start [x] *ptr [x] [acc] *ptr==*start
91                push 1 eq
92                skiz return
93                {&horner_iteration}
94                recurse
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use itertools::Itertools;
102    use twenty_first::math::polynomial::Polynomial;
103
104    use super::*;
105    use crate::test_prelude::*;
106
107    impl HornerEvaluationDynamicLength {
108        fn prepare_state(
109            &self,
110            coefficients: Vec<XFieldElement>,
111            coefficients_pointer: BFieldElement,
112            indeterminate: XFieldElement,
113        ) -> FunctionInitialState {
114            let mut memory = HashMap::default();
115            let mut stack = self.init_stack_for_isolated_run();
116            encode_to_memory(&mut memory, coefficients_pointer, &coefficients);
117
118            stack.push(coefficients_pointer);
119            stack.push(indeterminate.coefficients[2]);
120            stack.push(indeterminate.coefficients[1]);
121            stack.push(indeterminate.coefficients[0]);
122
123            FunctionInitialState { stack, memory }
124        }
125    }
126
127    impl Function for HornerEvaluationDynamicLength {
128        fn rust_shadow(
129            &self,
130            stack: &mut Vec<BFieldElement>,
131            memory: &mut HashMap<BFieldElement, BFieldElement>,
132        ) {
133            let x0 = stack.pop().unwrap();
134            let x1 = stack.pop().unwrap();
135            let x2 = stack.pop().unwrap();
136            let x = XFieldElement::new([x0, x1, x2]);
137
138            let address = stack.pop().unwrap();
139            let coefficients = *Vec::<XFieldElement>::decode_from_memory(memory, address).unwrap();
140            let polynomial = Polynomial::new(coefficients);
141
142            let value = polynomial.evaluate_in_same_field(x);
143            stack.push(value.coefficients[2]);
144            stack.push(value.coefficients[1]);
145            stack.push(value.coefficients[0]);
146        }
147
148        fn pseudorandom_initial_state(
149            &self,
150            seed: [u8; 32],
151            bench_case: Option<BenchmarkCase>,
152        ) -> FunctionInitialState {
153            let mut rng = StdRng::from_seed(seed);
154            let num_coefficients = match bench_case {
155                Some(BenchmarkCase::CommonCase) => 256,
156                Some(BenchmarkCase::WorstCase) => 512,
157                None => rng.random_range(0..1000),
158            };
159            let coefficients = (0..num_coefficients)
160                .map(|_| rng.random::<XFieldElement>())
161                .collect_vec();
162
163            let coefficients_pointer = bfe!(rng.random_range(0..(1u64 << 35)));
164
165            let indeterminate = rng.random::<XFieldElement>();
166
167            self.prepare_state(coefficients, coefficients_pointer, indeterminate)
168        }
169
170        fn corner_case_initial_states(&self) -> Vec<FunctionInitialState> {
171            let an_indeterminate = xfe!([1u64 << 45, 47, 49]);
172            let one_coefficient = self.prepare_state(xfe_vec![19991], bfe!(333), an_indeterminate);
173            let two_coefficients =
174                self.prepare_state(xfe_vec![19991, 299999992], bfe!(333), an_indeterminate);
175            let three_coefficients = self.prepare_state(
176                xfe_vec![19991, 299999992, 399999993],
177                bfe!(333),
178                an_indeterminate,
179            );
180
181            vec![one_coefficient, two_coefficients, three_coefficients]
182        }
183    }
184
185    #[test]
186    fn test() {
187        for _ in 0..10 {
188            ShadowedFunction::new(HornerEvaluationDynamicLength).test();
189        }
190    }
191}
192
193#[cfg(test)]
194mod bench {
195    use super::*;
196    use crate::test_prelude::*;
197
198    #[test]
199    fn benchmark() {
200        ShadowedFunction::new(HornerEvaluationDynamicLength).bench();
201    }
202}