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}