1use triton_vm::prelude::*;
2
3use crate::list::length::Length;
4use crate::prelude::*;
5
6#[derive(Debug, Clone, Eq, PartialEq, Hash)]
27pub struct Get {
28 element_type: DataType,
29}
30
31impl Get {
32 pub const INDEX_OUT_OF_BOUNDS_ERROR_ID: i128 = 380;
33
34 pub const MEM_PAGE_ACCESS_VIOLATION_ERROR_ID: i128 = 381;
37
38 pub fn new(element_type: DataType) -> Self {
43 Self::assert_element_type_is_supported(&element_type);
44
45 Self { element_type }
46 }
47
48 pub(crate) fn assert_element_type_is_supported(element_type: &DataType) {
53 let Some(static_len) = element_type.static_length() else {
54 panic!("element should have static length");
55 };
56 assert_ne!(0, static_len, "element must not be zero-sized");
57 }
58}
59
60impl BasicSnippet for Get {
61 fn inputs(&self) -> Vec<(DataType, String)> {
62 let list_type = DataType::List(Box::new(self.element_type.clone()));
63
64 vec![
65 (list_type, "*list".to_string()),
66 (DataType::U32, "index".to_string()),
67 ]
68 }
69
70 fn outputs(&self) -> Vec<(DataType, String)> {
71 vec![(self.element_type.clone(), "element".to_string())]
72 }
73
74 fn entrypoint(&self) -> String {
75 let element_type = self.element_type.label_friendly_name();
76 format!("tasmlib_list_get_element___{element_type}")
77 }
78
79 fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
80 let list_length = library.import(Box::new(Length));
81 let mul_with_element_size = match self.element_type.stack_size() {
82 1 => triton_asm!(), n => triton_asm!(push {n} mul),
84 };
85
86 triton_asm!(
87 {self.entrypoint()}:
90 dup 1
92 call {list_length} dup 1
94 lt assert error_id {Self::INDEX_OUT_OF_BOUNDS_ERROR_ID}
96
97 addi 1
98 {&mul_with_element_size}
99 split
103 pick 1
104 push 0
105 eq
106 assert error_id {Self::MEM_PAGE_ACCESS_VIOLATION_ERROR_ID}
107 add {&self.element_type.read_value_from_memory_pop_pointer()}
111 return
113 )
114 }
115}
116
117#[cfg(test)]
118pub(crate) mod tests {
119 use triton_vm::error::OpStackError::FailedU32Conversion;
120
121 use super::*;
122 use crate::U32_TO_USIZE_ERR;
123 use crate::rust_shadowing_helper_functions::list::insert_random_list;
124 use crate::rust_shadowing_helper_functions::list::list_get;
125 use crate::test_helpers::negative_test;
126 use crate::test_prelude::*;
127
128 impl Get {
129 fn set_up_initial_state(
130 &self,
131 list_length: usize,
132 index: usize,
133 list_pointer: BFieldElement,
134 ) -> AccessorInitialState {
135 let mut memory = HashMap::default();
136 insert_random_list(&self.element_type, list_pointer, list_length, &mut memory);
137
138 let mut stack = self.init_stack_for_isolated_run();
139 stack.push(list_pointer);
140 stack.push(bfe!(index));
141
142 AccessorInitialState { stack, memory }
143 }
144
145 pub fn random_len_idx_ptr(
146 bench_case: Option<BenchmarkCase>,
147 rng: &mut impl rand::Rng,
148 ) -> (usize, usize, BFieldElement) {
149 let (index, list_length) = match bench_case {
150 Some(BenchmarkCase::CommonCase) => (16, 32),
151 Some(BenchmarkCase::WorstCase) => (63, 64),
152 None => {
153 let list_length = rng.random_range(1..=100);
154 (rng.random_range(0..list_length), list_length)
155 }
156 };
157 let list_pointer = rng.random();
158
159 (list_length, index, list_pointer)
160 }
161 }
162
163 impl Accessor for Get {
164 fn rust_shadow(
165 &self,
166 stack: &mut Vec<BFieldElement>,
167 memory: &HashMap<BFieldElement, BFieldElement>,
168 ) {
169 let index: u32 = stack.pop().unwrap().try_into().unwrap();
170 let list_pointer = stack.pop().unwrap();
171
172 let index: usize = index.try_into().expect(U32_TO_USIZE_ERR);
173 let element_length = self.element_type.static_length().unwrap();
174 let element = list_get(list_pointer, index, memory, element_length);
175
176 stack.extend(element.into_iter().rev());
177 }
178
179 fn pseudorandom_initial_state(
180 &self,
181 seed: [u8; 32],
182 bench_case: Option<BenchmarkCase>,
183 ) -> AccessorInitialState {
184 let (list_length, index, list_pointer) =
185 Self::random_len_idx_ptr(bench_case, &mut StdRng::from_seed(seed));
186
187 self.set_up_initial_state(list_length, index, list_pointer)
188 }
189 }
190
191 #[test]
192 fn rust_shadow() {
193 for ty in [DataType::Bfe, DataType::Digest, DataType::I128] {
194 ShadowedAccessor::new(Get::new(ty)).test();
195 }
196 }
197
198 #[proptest]
199 fn out_of_bounds_access_crashes_vm(
200 #[strategy(0_usize..=1_000)] list_length: usize,
201 #[strategy(#list_length..1 << 32)] index: usize,
202 #[strategy(arb())] list_pointer: BFieldElement,
203 ) {
204 let get = Get::new(DataType::Bfe);
205 let initial_state = get.set_up_initial_state(list_length, index, list_pointer);
206 test_assertion_failure(
207 &ShadowedAccessor::new(get),
208 initial_state.into(),
209 &[Get::INDEX_OUT_OF_BOUNDS_ERROR_ID],
210 );
211 }
212
213 #[proptest]
214 fn too_large_indices_crash_vm(
215 #[strategy(1_usize << 32..)] index: usize,
216 #[strategy(arb())] list_pointer: BFieldElement,
217 ) {
218 let list_length = 0;
219 let get = Get::new(DataType::Bfe);
220 let initial_state = get.set_up_initial_state(list_length, index, list_pointer);
221 let expected_error = InstructionError::OpStackError(FailedU32Conversion(bfe!(index)));
222 negative_test(
223 &ShadowedAccessor::new(get),
224 initial_state.into(),
225 &[expected_error],
226 );
227 }
228
229 #[proptest(cases = 100)]
242 fn too_large_lists_crash_vm(
243 #[strategy(1_u64 << 22..1 << 32)] list_length: u64,
244 #[strategy((1 << 22) - 1..#list_length)] index: u64,
245 #[strategy(arb())] list_pointer: BFieldElement,
246 ) {
247 let mut memory = HashMap::default();
249 memory.insert(list_pointer, bfe!(list_length));
250
251 let tuple_ty = DataType::Tuple(vec![DataType::Bfe; 1 << 10]);
253 let get = Get::new(tuple_ty);
254 let mut stack = get.init_stack_for_isolated_run();
255 stack.push(list_pointer);
256 stack.push(bfe!(index));
257 let initial_state = AccessorInitialState { stack, memory };
258
259 test_assertion_failure(
260 &ShadowedAccessor::new(get),
261 initial_state.into(),
262 &[Get::MEM_PAGE_ACCESS_VIOLATION_ERROR_ID],
263 );
264 }
265}
266
267#[cfg(test)]
268mod benches {
269 use super::*;
270 use crate::test_prelude::*;
271
272 #[test]
273 fn benchmark() {
274 ShadowedAccessor::new(Get::new(DataType::Digest)).bench();
275 }
276}