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 parameters(&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 return_values(&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 super::*;
120 use crate::U32_TO_USIZE_ERR;
121 use crate::rust_shadowing_helper_functions::list::insert_random_list;
122 use crate::rust_shadowing_helper_functions::list::list_get;
123 use crate::test_prelude::*;
124
125 impl Get {
126 fn set_up_initial_state(
127 &self,
128 list_length: usize,
129 index: usize,
130 list_pointer: BFieldElement,
131 ) -> AccessorInitialState {
132 let mut memory = HashMap::default();
133 insert_random_list(&self.element_type, list_pointer, list_length, &mut memory);
134
135 let mut stack = self.init_stack_for_isolated_run();
136 stack.push(list_pointer);
137 stack.push(bfe!(index));
138
139 AccessorInitialState { stack, memory }
140 }
141
142 pub fn random_len_idx_ptr(
143 bench_case: Option<BenchmarkCase>,
144 rng: &mut impl Rng,
145 ) -> (usize, usize, BFieldElement) {
146 let (index, list_length) = match bench_case {
147 Some(BenchmarkCase::CommonCase) => (16, 32),
148 Some(BenchmarkCase::WorstCase) => (63, 64),
149 None => {
150 let list_length = rng.random_range(1..=100);
151 (rng.random_range(0..list_length), list_length)
152 }
153 };
154 let list_pointer = rng.random();
155
156 (list_length, index, list_pointer)
157 }
158 }
159
160 impl Accessor for Get {
161 fn rust_shadow(
162 &self,
163 stack: &mut Vec<BFieldElement>,
164 memory: &HashMap<BFieldElement, BFieldElement>,
165 ) -> Result<(), RustShadowError> {
166 let index: u32 = stack
167 .pop()
168 .ok_or(RustShadowError::StackUnderflow)?
169 .try_into()
170 .map_err(|_| RustShadowError::U64ToU32Error)?;
171 let list_pointer = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
172
173 let index: usize = index.try_into().expect(U32_TO_USIZE_ERR);
174 let element_length = self
175 .element_type
176 .static_length()
177 .ok_or(RustShadowError::Other)?;
178 let element = list_get(list_pointer, index, memory, element_length)?;
179
180 stack.extend(element.into_iter().rev());
181 Ok(())
182 }
183
184 fn pseudorandom_initial_state(
185 &self,
186 seed: [u8; 32],
187 bench_case: Option<BenchmarkCase>,
188 ) -> AccessorInitialState {
189 let (list_length, index, list_pointer) =
190 Self::random_len_idx_ptr(bench_case, &mut StdRng::from_seed(seed));
191
192 self.set_up_initial_state(list_length, index, list_pointer)
193 }
194 }
195
196 #[macro_rules_attr::apply(test)]
197 fn rust_shadow() {
198 for ty in [DataType::Bfe, DataType::Digest, DataType::I128] {
199 ShadowedAccessor::new(Get::new(ty)).test();
200 }
201 }
202
203 #[macro_rules_attr::apply(proptest)]
204 fn out_of_bounds_access_crashes_vm(
205 #[strategy(0_usize..=1_000)] list_length: usize,
206 #[strategy(#list_length..=u32::MAX.try_into().unwrap())] index: usize,
207 #[strategy(arb())] list_pointer: BFieldElement,
208 ) {
209 let get = Get::new(DataType::Bfe);
210 let initial_state = get.set_up_initial_state(list_length, index, list_pointer);
211 test_assertion_failure(
212 &ShadowedAccessor::new(get),
213 initial_state.into(),
214 &[Get::INDEX_OUT_OF_BOUNDS_ERROR_ID],
215 );
216 }
217
218 #[test_strategy::proptest]
220 #[cfg(not(target_arch = "wasm32"))]
221 fn too_large_indices_crash_vm(
222 #[strategy(1_usize << 32..)] index: usize,
223 #[strategy(arb())] list_pointer: BFieldElement,
224 ) {
225 use triton_vm::error::OpStackError::FailedU32Conversion;
226
227 let list_length = 0;
228 let get = Get::new(DataType::Bfe);
229 let initial_state = get.set_up_initial_state(list_length, index, list_pointer);
230 let expected_error = InstructionError::OpStackError(FailedU32Conversion(bfe!(index)));
231 crate::test_helpers::negative_test(
232 &ShadowedAccessor::new(get),
233 initial_state.into(),
234 &[expected_error],
235 );
236 }
237
238 #[macro_rules_attr::apply(proptest(cases = 100))]
251 fn too_large_lists_crash_vm(
252 #[strategy(1_u64 << 22..1 << 32)] list_length: u64,
253 #[strategy((1 << 22) - 1..#list_length)] index: u64,
254 #[strategy(arb())] list_pointer: BFieldElement,
255 ) {
256 let mut memory = HashMap::default();
258 memory.insert(list_pointer, bfe!(list_length));
259
260 let tuple_ty = DataType::Tuple(vec![DataType::Bfe; 1 << 10]);
262 let get = Get::new(tuple_ty);
263 let mut stack = get.init_stack_for_isolated_run();
264 stack.push(list_pointer);
265 stack.push(bfe!(index));
266 let initial_state = AccessorInitialState { stack, memory };
267
268 test_assertion_failure(
269 &ShadowedAccessor::new(get),
270 initial_state.into(),
271 &[Get::MEM_PAGE_ACCESS_VIOLATION_ERROR_ID],
272 );
273 }
274}
275
276#[cfg(test)]
277mod benches {
278 use super::*;
279 use crate::test_prelude::*;
280
281 #[macro_rules_attr::apply(test)]
282 fn benchmark() {
283 ShadowedAccessor::new(Get::new(DataType::Digest)).bench();
284 }
285}