tasm_lib/list/
push.rs

1use std::collections::HashMap;
2
3use triton_vm::prelude::*;
4
5use crate::list::get::Get;
6use crate::prelude::*;
7use crate::traits::basic_snippet::Reviewer;
8use crate::traits::basic_snippet::SignOffFingerprint;
9
10/// Push an element to a list. Performs bounds check.
11///
12/// Only supports lists with [statically sized](BFieldCodec::static_length)
13/// elements. The element's static size must be in range `1..=15`.
14///
15/// ### Behavior
16///
17/// ```text
18/// BEFORE: _ *list [element: ElementType]
19/// AFTER:  _
20/// ```
21///
22/// ### Preconditions
23///
24/// - the argument `*list` points to a properly [`BFieldCodec`]-encoded list
25/// - all input arguments are properly [`BFieldCodec`] encoded
26///
27/// ### Postconditions
28///
29/// None.
30#[derive(Debug, Clone, Eq, PartialEq, Hash)]
31pub struct Push {
32    element_type: DataType,
33}
34
35impl Push {
36    /// Any part of the list is outside the allocated memory page.
37    /// See the [memory convention][crate::memory] for more details.
38    pub const MEM_PAGE_ACCESS_VIOLATION_ERROR_ID: i128 = 410;
39
40    /// # Panics
41    ///
42    /// Panics
43    /// - if the element has [dynamic length][BFieldCodec::static_length], or
44    /// - if the static length is 0, or
45    /// - if the static length is larger than or equal to 16.
46    pub fn new(element_type: DataType) -> Self {
47        // need to access argument `*list`
48        assert!(element_type.stack_size() < 16);
49        Get::assert_element_type_is_supported(&element_type);
50
51        Self { element_type }
52    }
53}
54
55impl BasicSnippet for Push {
56    fn inputs(&self) -> Vec<(DataType, String)> {
57        let list_type = DataType::List(Box::new(self.element_type.clone()));
58        let element_type = self.element_type.clone();
59
60        vec![
61            (list_type, "*list".to_string()),
62            (element_type, "element".to_string()),
63        ]
64    }
65
66    fn outputs(&self) -> Vec<(DataType, String)> {
67        vec![]
68    }
69
70    fn entrypoint(&self) -> String {
71        let element_type = self.element_type.label_friendly_name();
72        format!("tasmlib_list_push___{element_type}")
73    }
74
75    fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
76        let mul_with_element_size = match self.element_type.stack_size() {
77            1 => triton_asm!(), // no-op
78            n => triton_asm!(push {n} mul),
79        };
80        let add_element_size_minus_1 = match self.element_type.stack_size() {
81            1 => triton_asm!(), // no-op
82            n => triton_asm!(addi {n - 1}),
83        };
84
85        triton_asm!(
86            // BEFORE: _ *list [element]
87            // AFTER:  _
88            {self.entrypoint()}:
89                pick {self.element_type.stack_size()}
90                                    // _ [element] *list
91                read_mem 1          // _ [element] length *list
92                addi 1
93
94                /* update list length */
95                dup 1               // _ [element] length *list length
96                addi 1              // _ [element] length *list new_length
97                pick 1
98                write_mem 1         // _ [element] length (*list + 1)
99
100                /* compute element's offset in list */
101                pick 1
102                {&mul_with_element_size}
103                                    // _ [element] (*list + 1) offset
104
105                /* assert access is within one memory page */
106                dup 0
107                {&add_element_size_minus_1}
108                                    // _ [element] *list offset highest_word_idx
109                split
110                pop 1
111                push 0
112                eq
113                assert error_id {Self::MEM_PAGE_ACCESS_VIOLATION_ERROR_ID}
114                                    // _ [element] *list offset
115
116                /* finally, write that thing */
117                add                 // _ [element] *element
118                {&self.element_type.write_value_to_memory_pop_pointer()}
119                                    // _
120                return
121        )
122    }
123
124    fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
125        let mut sign_offs = HashMap::new();
126        match self.element_type.stack_size() {
127            1 => _ = sign_offs.insert(Reviewer("ferdinand"), 0x292abedacc166a44.into()),
128            2 => _ = sign_offs.insert(Reviewer("ferdinand"), 0x713379aa2ec2f141.into()),
129            3 => _ = sign_offs.insert(Reviewer("ferdinand"), 0x187fb6005d7699cc.into()),
130            5 => _ = sign_offs.insert(Reviewer("ferdinand"), 0x456653f3204bda08.into()),
131            _ => (),
132        }
133
134        sign_offs
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::rust_shadowing_helper_functions::list::insert_random_list;
142    use crate::rust_shadowing_helper_functions::list::list_push;
143    use crate::test_prelude::*;
144
145    impl Push {
146        fn set_up_initial_state(
147            &self,
148            list_length: usize,
149            list_pointer: BFieldElement,
150            element: Vec<BFieldElement>,
151        ) -> FunctionInitialState {
152            let mut memory = HashMap::default();
153            insert_random_list(&self.element_type, list_pointer, list_length, &mut memory);
154
155            let mut stack = self.init_stack_for_isolated_run();
156            stack.push(list_pointer);
157            stack.extend(element.into_iter().rev());
158
159            FunctionInitialState { stack, memory }
160        }
161    }
162
163    impl Function for Push {
164        fn rust_shadow(
165            &self,
166            stack: &mut Vec<BFieldElement>,
167            memory: &mut HashMap<BFieldElement, BFieldElement>,
168        ) {
169            let element = (0..self.element_type.stack_size())
170                .map(|_| stack.pop().unwrap())
171                .collect();
172            let list_pointer = stack.pop().unwrap();
173
174            list_push(list_pointer, element, memory);
175        }
176
177        fn pseudorandom_initial_state(
178            &self,
179            seed: [u8; 32],
180            bench_case: Option<BenchmarkCase>,
181        ) -> FunctionInitialState {
182            let mut rng = StdRng::from_seed(seed);
183            let (list_length, _, list_pointer) = Get::random_len_idx_ptr(bench_case, &mut rng);
184            let element = self.element_type.seeded_random_element(&mut rng);
185
186            self.set_up_initial_state(list_length, list_pointer, element)
187        }
188    }
189
190    #[test]
191    fn rust_shadow() {
192        for ty in [
193            DataType::Bool,
194            DataType::Bfe,
195            DataType::U32,
196            DataType::U64,
197            DataType::Xfe,
198            DataType::Digest,
199        ] {
200            ShadowedFunction::new(Push::new(ty)).test()
201        }
202    }
203
204    /// See similar test for [`Get`] for an explanation.
205    #[proptest(cases = 100)]
206    fn too_large_lists_crash_vm(
207        #[strategy(1_u64 << 29..1 << 32)] list_length: u64,
208        #[strategy(arb())] list_pointer: BFieldElement,
209    ) {
210        // spare host machine RAM: pretend every element is all-zeros
211        let mut memory = HashMap::default();
212        memory.insert(list_pointer, bfe!(list_length));
213
214        // largest supported element type
215        let tuple_ty = DataType::Tuple(vec![DataType::Bfe; 15]);
216        let push = Push::new(tuple_ty);
217
218        let mut stack = push.init_stack_for_isolated_run();
219        stack.push(list_pointer);
220        stack.extend(bfe_array![0; 15]);
221        let initial_state = AccessorInitialState { stack, memory };
222
223        test_assertion_failure(
224            &ShadowedFunction::new(push),
225            initial_state.into(),
226            &[Push::MEM_PAGE_ACCESS_VIOLATION_ERROR_ID],
227        );
228    }
229}
230
231#[cfg(test)]
232mod benches {
233    use super::*;
234    use crate::test_prelude::*;
235
236    #[test]
237    fn benchmark() {
238        ShadowedFunction::new(Push::new(DataType::Digest));
239    }
240}