miden_stdlib/handlers/
sorted_array.rs1use alloc::{vec, vec::Vec};
2
3use miden_core::{EventName, Felt, FieldElement, LexicographicWord, Word};
4use miden_processor::{AdviceMutation, EventError, MemoryError, ProcessState};
5
6pub const LOWERBOUND_ARRAY_EVENT_NAME: EventName =
8 EventName::new("stdlib::collections::sorted_array::lowerbound_array");
9
10pub const LOWERBOUND_KEY_VALUE_EVENT_NAME: EventName =
12 EventName::new("stdlib::collections::sorted_array::lowerbound_key_value");
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15enum KeySize {
16 Full,
17 Half,
18}
19
20pub fn handle_lowerbound_array(process: &ProcessState) -> Result<Vec<AdviceMutation>, EventError> {
35 push_lowerbound_result(process, 4, KeySize::Full)
36}
37
38pub fn handle_lowerbound_key_value(
56 process: &ProcessState,
57) -> Result<Vec<AdviceMutation>, EventError> {
58 let use_full_key = process.get_stack_item(7);
59
60 let key_size = match use_full_key.as_int() {
61 0 => KeySize::Half,
62 1 => KeySize::Full,
63 _ => {
64 return Err(EventError::from(alloc::format!(
65 "use_full_key must be 0 or 1, was {use_full_key}"
66 )));
67 },
68 };
69
70 push_lowerbound_result(process, 8, key_size)
71}
72
73const KEY_OFFSET: usize = 1;
75const START_ADDR_OFFSET: usize = 5;
76const END_ADDR_OFFSET: usize = 6;
77
78fn push_lowerbound_result(
79 process: &ProcessState,
80 stride: u32,
81 key_size: KeySize,
82) -> Result<Vec<AdviceMutation>, EventError> {
83 assert!(stride == 4 || stride == 8);
85
86 let key = LexicographicWord::new(process.get_stack_word_be(KEY_OFFSET));
88 let addr_range = process.get_mem_addr_range(START_ADDR_OFFSET, END_ADDR_OFFSET)?;
89
90 if addr_range.start % 4 != 0 {
92 return Err(MemoryError::unaligned_word_access(
93 addr_range.start,
94 process.ctx(),
95 Felt::from(process.clk()),
96 &(),
97 )
98 .into());
99 }
100
101 if (addr_range.end - addr_range.start) % stride != 0 {
104 if stride == 4 {
105 return Err(
106 SortedArrayError::InvalidArrayRange { size: addr_range.len() as u32 }.into()
107 );
108 } else {
109 return Err(
110 SortedArrayError::InvalidKeyValueRange { size: addr_range.len() as u32 }.into()
111 );
112 }
113 }
114
115 if addr_range.is_empty() {
117 return Ok(vec![AdviceMutation::extend_stack(vec![
118 Felt::from(false),
119 Felt::from(addr_range.end),
120 ])]);
121 }
122
123 let get_word = {
125 |addr: u32| {
126 process
127 .get_mem_word(process.ctx(), addr)
128 .map(|word| word_to_search_key(word.unwrap_or_default(), key_size))
129 }
130 };
131
132 let mut was_key_found = false;
133 let mut result = None;
134
135 let mut previous_word = get_word(addr_range.start)?;
137 if previous_word >= key {
138 was_key_found = previous_word == key;
139 result = Some(addr_range.start);
140 }
141
142 for addr in addr_range.clone().step_by(stride as usize).skip(1) {
144 let word = get_word(addr)?;
145 if word < previous_word {
146 return Err(SortedArrayError::NotAscendingOrder {
147 index: addr,
148 value: word.into(),
149 predecessor: previous_word.into(),
150 }
151 .into());
152 }
153 if word >= key && result.is_none() {
154 was_key_found = word == key;
155 result = Some(addr);
156 }
157 previous_word = word;
158 }
159
160 Ok(vec![AdviceMutation::extend_stack(vec![
161 Felt::from(was_key_found),
162 Felt::from(result.unwrap_or(addr_range.end)),
163 ])])
164}
165
166fn word_to_search_key(mut word: Word, key_size: KeySize) -> LexicographicWord {
172 match key_size {
173 KeySize::Full => LexicographicWord::new(word),
174 KeySize::Half => {
175 word[0] = Felt::ZERO;
176 word[1] = Felt::ZERO;
177
178 LexicographicWord::new(word)
179 },
180 }
181}
182
183#[derive(Debug, thiserror::Error)]
188pub enum SortedArrayError {
189 #[error("element at index {index} ({value}) is smaller than the predecessor ({predecessor})")]
191 NotAscendingOrder {
192 index: u32,
193 value: Word,
194 predecessor: Word,
195 },
196
197 #[error("array size must be divisible by 4, but was {size}")]
199 InvalidArrayRange { size: u32 },
200
201 #[error("key-value array must have size divisible by 4 or 8, but was {size}")]
203 InvalidKeyValueRange { size: u32 },
204}