miden_tx/host/
link_map.rs

1use alloc::vec::Vec;
2use core::cmp::Ordering;
3
4use miden_objects::{Felt, LexicographicWord, Word, ZERO};
5use miden_processor::fast::ExecutionOutput;
6use miden_processor::{AdviceMutation, ContextId, EventError, ProcessState};
7
8// LINK MAP
9// ================================================================================================
10
11/// A map based on a sorted linked list.
12///
13/// This type enables access to the list in kernel memory.
14///
15/// See link_map.masm for docs.
16///
17/// # Warning
18///
19/// The functions on this type assume that the provided map_ptr points to a valid map in the
20/// provided memory viewer. If those assumptions are violated, the functions may panic.
21#[derive(Clone, Copy)]
22pub struct LinkMap<'process> {
23    map_ptr: u32,
24    mem: &'process MemoryViewer<'process>,
25}
26
27impl<'process> LinkMap<'process> {
28    // CONSTRUCTOR
29    // --------------------------------------------------------------------------------------------
30
31    /// Creates a new link map from the provided map_ptr in the provided process.
32    pub fn new(map_ptr: Felt, mem: &'process MemoryViewer<'process>) -> Self {
33        let map_ptr: u32 = map_ptr.try_into().expect("map_ptr must be a valid u32");
34
35        Self { map_ptr, mem }
36    }
37
38    // PUBLIC METHODS
39    // --------------------------------------------------------------------------------------------
40
41    /// Handles a `LINK_MAP_SET_EVENT` emitted from a VM.
42    ///
43    /// Expected operand stack state before: [map_ptr, KEY, NEW_VALUE]
44    /// Advice stack state after: [set_operation, entry_ptr]
45    pub fn handle_set_event(process: &ProcessState<'_>) -> Result<Vec<AdviceMutation>, EventError> {
46        let map_ptr = process.get_stack_item(1);
47        let map_key = process.get_stack_word_be(2);
48
49        let mem_viewer = MemoryViewer::ProcessState(process);
50        let link_map = LinkMap::new(map_ptr, &mem_viewer);
51
52        let (set_op, entry_ptr) = link_map.compute_set_operation(LexicographicWord::from(map_key));
53
54        Ok(vec![AdviceMutation::extend_stack([
55            Felt::from(set_op as u8),
56            Felt::from(entry_ptr),
57        ])])
58    }
59
60    /// Handles a `LINK_MAP_GET_EVENT` emitted from a VM.
61    ///
62    /// Expected operand stack state before: [map_ptr, KEY]
63    /// Advice stack state after: [get_operation, entry_ptr]
64    pub fn handle_get_event(process: &ProcessState<'_>) -> Result<Vec<AdviceMutation>, EventError> {
65        let map_ptr = process.get_stack_item(1);
66        let map_key = process.get_stack_word_be(2);
67
68        let mem_viewer = MemoryViewer::ProcessState(process);
69        let link_map = LinkMap::new(map_ptr, &mem_viewer);
70        let (get_op, entry_ptr) = link_map.compute_get_operation(LexicographicWord::from(map_key));
71
72        Ok(vec![AdviceMutation::extend_stack([
73            Felt::from(get_op as u8),
74            Felt::from(entry_ptr),
75        ])])
76    }
77
78    /// Returns `true` if the map is empty, `false` otherwise.
79    pub fn is_empty(&self) -> bool {
80        self.head().is_none()
81    }
82
83    /// Returns an iterator over the link map entries.
84    pub fn iter(&self) -> impl Iterator<Item = Entry> {
85        LinkMapIter {
86            current_entry_ptr: self.head().unwrap_or(0),
87            map: *self,
88        }
89    }
90
91    // PRIVATE METHODS
92    // --------------------------------------------------------------------------------------------
93
94    /// Returns the entry pointer at the head of the map or `None` if the map is empty.
95    fn head(&self) -> Option<u32> {
96        // Returns None if the value was either not yet initialized or points to 0.
97        // It can point to 0 for example if a get operation is executed before a set operation,
98        // which initializes the value in memory to 0 but does not change it.
99        self.mem.get_kernel_mem_element(self.map_ptr).and_then(|head_ptr| {
100            if head_ptr == ZERO {
101                None
102            } else {
103                Some(u32::try_from(head_ptr).expect("head ptr should be a valid ptr"))
104            }
105        })
106    }
107
108    /// Returns the [`Entry`] at the given pointer.
109    fn entry(&self, entry_ptr: u32) -> Entry {
110        let key = self.key(entry_ptr);
111        let (value0, value1) = self.value(entry_ptr);
112        let metadata = self.metadata(entry_ptr);
113
114        Entry {
115            ptr: entry_ptr,
116            metadata,
117            key,
118            value0,
119            value1,
120        }
121    }
122
123    /// Returns the key of the entry at the given pointer.
124    fn key(&self, entry_ptr: u32) -> LexicographicWord {
125        LexicographicWord::from(
126            self.mem
127                .get_kernel_mem_word(entry_ptr + 4)
128                .expect("entry pointer should be valid"),
129        )
130    }
131
132    /// Returns the values of the entry at the given pointer.
133    fn value(&self, entry_ptr: u32) -> (Word, Word) {
134        let value0 = self
135            .mem
136            .get_kernel_mem_word(entry_ptr + 8)
137            .expect("entry pointer should be valid");
138        let value1 = self
139            .mem
140            .get_kernel_mem_word(entry_ptr + 12)
141            .expect("entry pointer should be valid");
142        (value0, value1)
143    }
144
145    /// Returns the metadata of the entry at the given pointer.
146    fn metadata(&self, entry_ptr: u32) -> EntryMetadata {
147        let entry_metadata =
148            self.mem.get_kernel_mem_word(entry_ptr).expect("entry pointer should be valid");
149
150        let map_ptr = entry_metadata[0];
151        let map_ptr = map_ptr.try_into().expect("entry_ptr should point to a u32 map_ptr");
152
153        let prev_entry_ptr = entry_metadata[1];
154        let prev_entry_ptr = prev_entry_ptr
155            .try_into()
156            .expect("entry_ptr should point to a u32 prev_entry_ptr");
157
158        let next_entry_ptr = entry_metadata[2];
159        let next_entry_ptr = next_entry_ptr
160            .try_into()
161            .expect("entry_ptr should point to a u32 next_entry_ptr");
162
163        EntryMetadata { map_ptr, prev_entry_ptr, next_entry_ptr }
164    }
165
166    /// Computes what needs to be done to insert the given key into the link map.
167    ///
168    /// If the key already exists in the map, then its value must be updated and
169    /// [`SetOperation::Update`] and the pointer to the existing entry are returned.
170    ///
171    /// If the key does not exist in the map, find the place where it has to be inserted. This can
172    /// be at the head of the list ([`SetOperation::InsertAtHead`]) if the key is smaller than all
173    /// existing keys or if the map is empty. Otherwise it is after an existing entry
174    /// ([`SetOperation::InsertAfterEntry`]) in which case the key must be greater than the entry's
175    /// key after which it is inserted and smaller than the entry before which it is inserted
176    /// (unless it is the end of the map).
177    fn compute_set_operation(&self, key: LexicographicWord) -> (SetOperation, u32) {
178        let Some(current_head) = self.head() else {
179            return (SetOperation::InsertAtHead, 0);
180        };
181
182        let mut last_entry_ptr: u32 = current_head;
183
184        for entry in self.iter() {
185            match key.cmp(&entry.key) {
186                Ordering::Equal => {
187                    return (SetOperation::Update, entry.ptr);
188                },
189                Ordering::Less => {
190                    if entry.ptr == current_head {
191                        return (SetOperation::InsertAtHead, entry.ptr);
192                    }
193
194                    break;
195                },
196                Ordering::Greater => {
197                    last_entry_ptr = entry.ptr;
198                },
199            }
200        }
201
202        (SetOperation::InsertAfterEntry, last_entry_ptr)
203    }
204
205    /// Computes a get operation for a key in a link map.
206    ///
207    /// If the key exists, then [`GetOperation::Found`] is returned and the pointer to it.
208    ///
209    /// If it does not exist, its absence must be proven, otherwise the host could lie. To do that,
210    /// the in-kernel link map validates that the key is not in the list, so this function returns
211    /// information pointing to the entry where the key would be if it existed.
212    ///
213    /// The way to compute this is the same as a set operation, so this function simply remaps its
214    /// output.
215    fn compute_get_operation(&self, key: LexicographicWord) -> (GetOperation, u32) {
216        let (set_op, entry_ptr) = self.compute_set_operation(key);
217        let get_op = match set_op {
218            SetOperation::Update => GetOperation::Found,
219            SetOperation::InsertAtHead => GetOperation::AbsentAtHead,
220            SetOperation::InsertAfterEntry => GetOperation::AbsentAfterEntry,
221        };
222        (get_op, entry_ptr)
223    }
224}
225
226// LINK MAP ITER
227// ================================================================================================
228
229/// An iterator over a [`LinkMap`].
230struct LinkMapIter<'process> {
231    current_entry_ptr: u32,
232    map: LinkMap<'process>,
233}
234
235impl<'process> Iterator for LinkMapIter<'process> {
236    type Item = Entry;
237
238    fn next(&mut self) -> Option<Self::Item> {
239        if self.current_entry_ptr == 0 {
240            return None;
241        }
242
243        let current_entry = self.map.entry(self.current_entry_ptr);
244
245        self.current_entry_ptr = current_entry.metadata.next_entry_ptr;
246
247        Some(current_entry)
248    }
249}
250
251// LINK MAP TYPES
252// ================================================================================================
253
254/// An entry in a [`LinkMap`].
255///
256/// Exposed for testing purposes only.
257#[derive(Debug, Clone, Copy)]
258pub struct Entry {
259    pub ptr: u32,
260    pub metadata: EntryMetadata,
261    pub key: LexicographicWord,
262    pub value0: Word,
263    pub value1: Word,
264}
265
266/// An entry's metadata in a [`LinkMap`].
267///
268/// Exposed for testing purposes only.
269#[derive(Debug, Clone, Copy)]
270pub struct EntryMetadata {
271    pub map_ptr: u32,
272    pub prev_entry_ptr: u32,
273    pub next_entry_ptr: u32,
274}
275
276// HELPER TYPES
277// ================================================================================================
278
279/// The operation needed to get a key or prove its absence.
280#[derive(Debug, Clone, Copy)]
281#[repr(u8)]
282enum GetOperation {
283    Found = 0,
284    AbsentAtHead = 1,
285    AbsentAfterEntry = 2,
286}
287
288/// The operation needed to set a key.
289#[derive(Debug, Clone, Copy)]
290#[repr(u8)]
291enum SetOperation {
292    Update = 0,
293    InsertAtHead = 1,
294    InsertAfterEntry = 2,
295}
296
297// MEMORY VIEWER
298// ================================================================================================
299
300/// A abstraction over ways to view a process' memory.
301///
302/// More specifically, it allows using a [`LinkMap`] both with a [`ProcessState`], i.e. a process
303/// that is actively executing and also an [`ExecutionOutput`], i.e. a process that has finished
304/// execution.
305///
306/// This should all go away again once we change a LinkMap's implementation to be based on an actual
307/// map type instead of viewing a process' memory directly.
308pub enum MemoryViewer<'mem> {
309    ProcessState(&'mem ProcessState<'mem>),
310    ExecutionOutputs(&'mem ExecutionOutput),
311}
312
313impl<'mem> MemoryViewer<'mem> {
314    /// Reads an element from transaction kernel memory.
315    fn get_kernel_mem_element(&self, addr: u32) -> Option<Felt> {
316        match self {
317            MemoryViewer::ProcessState(process_state) => {
318                process_state.get_mem_value(ContextId::root(), addr)
319            },
320            MemoryViewer::ExecutionOutputs(_execution_output) => {
321                // TODO: Use Memory::read_element once it no longer requires &mut self.
322                // https://github.com/0xMiden/miden-vm/issues/2237
323
324                // Copy of how Memory::read_element is implemented in Miden VM.
325                let idx = addr % miden_objects::WORD_SIZE as u32;
326                let word_addr = addr - idx;
327
328                Some(self.get_kernel_mem_word(word_addr)?[idx as usize])
329            },
330        }
331    }
332
333    /// Reads a word from transaction kernel memory.
334    fn get_kernel_mem_word(&self, addr: u32) -> Option<Word> {
335        match self {
336            MemoryViewer::ProcessState(process_state) => process_state
337                .get_mem_word(ContextId::root(), addr)
338                .expect("address should be word-aligned"),
339            MemoryViewer::ExecutionOutputs(execution_output) => {
340                let tx_kernel_context = ContextId::root();
341                let clk = 0u32;
342                let err_ctx = ();
343
344                // Note that this never returns None even if the location is uninitialized, but the
345                // link map does not rely on this.
346                Some(
347                    execution_output
348                        .memory
349                        .read_word(tx_kernel_context, Felt::from(addr), clk.into(), &err_ctx)
350                        .expect("expected address to be word-aligned"),
351                )
352            },
353        }
354    }
355}