Skip to main content

tasm_lib/
memory.rs

1//! Memory convention used in this crate:
2//!
3//! - A memory page is contiguous memory of size 2^32 words, aligned to 2^32 word boundaries.
4//!   There is one exception: due to the [base field's prime][prime] being 2^64 - 2^32 + 1,
5//!   the last page, starting at address 2^64 - 2^32, is of size 1.
6//! - The dynamic allocator lives at address [`DYN_MALLOC_ADDRESS`][dyn_malloc_addr], _i.e._, -1.
7//!   It is a single word, containing a counter of allocated pages.
8//!   It occupies the only page that is not of size 2^32 words.
9//! - Page 0 is reserved for non-deterministically initialized memory.
10//! - The last full page, number (2^32)-2, starting at address 2^64 - 2ยท(2^32),
11//!   is reserved for [static allocations][static_malloc_addr].
12//! - The two preceding pages, number 2^32-4 and 2^32-3 are used by the
13//!   [STARK verifier][stark_verifier] (when standard memory layouts are used).
14//! - Pages 1 through 2^31-1 are dynamically allocated by the
15//!   [dynamic allocator][dynamic_allocator] snippet.
16//! - Pages 2^31 through 2^32-5 are not in use by this crate.
17//!
18//! [prime]: BFieldElement::P
19//! [dyn_malloc_addr]: dyn_malloc::DYN_MALLOC_ADDRESS
20//! [static_malloc_addr]: crate::library::STATIC_MEMORY_FIRST_ADDRESS
21//! [stark_verifier]: crate::verifier::stark_verify::StarkVerify
22//! [dynamic_allocator]: dyn_malloc::DynMalloc
23
24use std::collections::HashMap;
25
26use num::One;
27use triton_vm::memory_layout::MemoryRegion;
28use triton_vm::prelude::*;
29
30pub mod dyn_malloc;
31pub mod memcpy;
32
33/// Non-deterministically initialized memory lives in the range $[0: 2^{32})$
34///
35/// See the [memory convention][self] for details.
36pub const FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS: BFieldElement =
37    BFieldElement::new(0);
38
39pub const LAST_ADDRESS_AVAILABLE_FOR_NON_DETERMINISTICALLY_ALLOCATED_MEMORY: BFieldElement =
40    BFieldElement::new(u32::MAX as u64);
41
42/// Returns the address of the last populated word belonging to the memory region
43/// designated for non-determinism.
44pub fn last_populated_nd_memory_address(
45    memory: &HashMap<BFieldElement, BFieldElement>,
46) -> Option<BFieldElement> {
47    memory
48        .keys()
49        .map(|b| b.value())
50        .filter(|u| *u <= LAST_ADDRESS_AVAILABLE_FOR_NON_DETERMINISTICALLY_ALLOCATED_MEMORY.value())
51        .max()
52        .map(|address| bfe!(address))
53}
54
55/// Returns the address of the first unpopulated word belonging to the memory
56/// region designated for non-determinism.
57pub fn first_free_nd_address(
58    memory: &HashMap<BFieldElement, BFieldElement>,
59) -> Option<BFieldElement> {
60    let last_populated = last_populated_nd_memory_address(memory);
61    match last_populated {
62        None => Some(FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS),
63        Some(last_populated) => match last_populated {
64            LAST_ADDRESS_AVAILABLE_FOR_NON_DETERMINISTICALLY_ALLOCATED_MEMORY => None,
65            addr => Some(addr + BFieldElement::one()),
66        },
67    }
68}
69
70pub fn nd_memory_region() -> MemoryRegion {
71    let size = LAST_ADDRESS_AVAILABLE_FOR_NON_DETERMINISTICALLY_ALLOCATED_MEMORY.value()
72        - FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS.value();
73
74    MemoryRegion::new(FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS, size)
75}
76
77/// Stores the encoding of the given object into memory at the given address, and returns
78/// the address of the first untouched memory cell after.
79pub fn encode_to_memory<T: BFieldCodec>(
80    memory: &mut HashMap<BFieldElement, BFieldElement>,
81    address: BFieldElement,
82    object: &T,
83) -> BFieldElement {
84    let encoding = object.encode();
85    for (i, e) in encoding.iter().enumerate() {
86        memory.insert(address + bfe!(i), *e);
87    }
88    address + bfe!(encoding.len())
89}
90
91/// Return the code to read `n` words from memory. Top of stack must point
92/// to last word of words to read. Leaves mutated pointer on top of stack.
93///
94/// ```text
95/// BEFORE: _ *last_word
96/// AFTER:  _ [loaded_words; n] (*first_word - 1)
97/// ```
98pub fn load_words_from_memory_leave_pointer(n: usize) -> Vec<LabelledInstruction> {
99    let num_full_chunk_reads = n / 5;
100    let num_remaining_words = n % 5;
101    let mut instructions = triton_asm![read_mem 5; num_full_chunk_reads];
102    if num_remaining_words > 0 {
103        instructions.extend(triton_asm!(read_mem {
104            num_remaining_words
105        }));
106    }
107    instructions
108}
109
110/// Return the code to read `n` words from memory. Top of stack must point
111/// to last word of words to read. Pops the memory pointer from the stack.
112///
113/// ```text
114/// BEFORE: _ *last_word
115/// AFTER:  _ [loaded_words; n]
116/// ```
117pub fn load_words_from_memory_pop_pointer(n: usize) -> Vec<LabelledInstruction> {
118    let instructions = load_words_from_memory_leave_pointer(n);
119
120    triton_asm!(
121        {&instructions}
122        pop 1
123    )
124}
125
126/// Return the code to write `n` words to memory. Leaves a modified pointer
127/// on the stack.
128///
129/// ```text
130/// BEFORE: _ [words_to_write; n] *first_word
131/// AFTER:  _ (*last_word + 1)
132/// ```
133pub fn write_words_to_memory_leave_pointer(n: usize) -> Vec<LabelledInstruction> {
134    let num_full_chunk_reads = n / 5;
135    let num_remaining_words = n % 5;
136    let mut instructions = vec![triton_instr!(write_mem 5); num_full_chunk_reads];
137    if num_remaining_words > 0 {
138        instructions.extend(triton_asm!(write_mem {
139            num_remaining_words
140        }));
141    }
142
143    instructions
144}
145
146/// Return the code to write `n` words to memory. Pops the memory pointer.
147///
148/// ```text
149/// BEFORE: _ [words_to_write; n] *first_word
150/// AFTER:  _
151/// ```
152pub fn write_words_to_memory_pop_pointer(n: usize) -> Vec<LabelledInstruction> {
153    let instructions = write_words_to_memory_leave_pointer(n);
154
155    triton_asm!(
156        {&instructions}
157        pop 1
158    )
159}
160
161#[cfg(test)]
162mod tests {
163    use std::collections::HashMap;
164
165    use num::Zero;
166
167    use super::*;
168    use crate::test_prelude::*;
169
170    #[macro_rules_attr::apply(test)]
171    fn last_populated_nd_memory_address_looks_sane() {
172        let empty_memory = HashMap::default();
173        assert!(last_populated_nd_memory_address(&empty_memory).is_none());
174        assert_eq!(
175            Some(BFieldElement::zero()),
176            first_free_nd_address(&empty_memory)
177        );
178
179        let empty_nd_region: HashMap<_, _> = [(bfe!(1u64 << 32), BFieldElement::new(42))]
180            .into_iter()
181            .collect();
182        assert!(last_populated_nd_memory_address(&empty_nd_region).is_none());
183        assert_eq!(
184            Some(BFieldElement::zero()),
185            first_free_nd_address(&empty_nd_region)
186        );
187
188        for address in [0, 1, 100, u32::MAX - 3, u32::MAX - 1, u32::MAX] {
189            let one_populated_word: HashMap<_, _> = [(bfe!(address), BFieldElement::new(42))]
190                .into_iter()
191                .collect();
192            assert_eq!(
193                Some(bfe!(address)),
194                last_populated_nd_memory_address(&one_populated_word)
195            );
196        }
197
198        for address in [1, 100, u32::MAX - 3, u32::MAX - 1, u32::MAX] {
199            let two_populated_words: HashMap<_, _> = [
200                (bfe!(address), BFieldElement::new(42)),
201                (bfe!(address - 1), BFieldElement::new(42)),
202            ]
203            .into_iter()
204            .collect();
205            assert_eq!(
206                Some(bfe!(address)),
207                last_populated_nd_memory_address(&two_populated_words)
208            );
209        }
210
211        let much_poulated_zero_is_empty: HashMap<_, _> =
212            (1..4000).map(|address| (bfe!(address), bfe!(42))).collect();
213        assert_eq!(
214            Some(bfe!(3999)),
215            last_populated_nd_memory_address(&much_poulated_zero_is_empty)
216        );
217        assert_eq!(
218            Some(bfe!(4000)),
219            first_free_nd_address(&much_poulated_zero_is_empty)
220        );
221
222        let much_poulated_zero_populated: HashMap<_, _> =
223            (0..4021).map(|address| (bfe!(address), bfe!(42))).collect();
224        assert_eq!(
225            Some(bfe!(4020)),
226            last_populated_nd_memory_address(&much_poulated_zero_populated)
227        );
228        assert_eq!(
229            Some(bfe!(4021)),
230            first_free_nd_address(&much_poulated_zero_populated)
231        );
232
233        let scattered_population: HashMap<_, _> = [(bfe!(30), bfe!(42)), (bfe!(2000), bfe!(42))]
234            .into_iter()
235            .collect();
236        assert_eq!(
237            Some(bfe!(2000)),
238            last_populated_nd_memory_address(&scattered_population)
239        );
240        assert_eq!(
241            Some(bfe!(2001)),
242            first_free_nd_address(&scattered_population)
243        );
244    }
245
246    #[macro_rules_attr::apply(test)]
247    fn first_free_nd_address_looks_sane() {
248        for address in [0, 1, 100, u32::MAX - 3, u32::MAX - 3] {
249            let one_populated_word: HashMap<_, _> = [(bfe!(address), BFieldElement::new(42))]
250                .into_iter()
251                .collect();
252            assert_eq!(
253                Some(bfe!(address + 1)),
254                first_free_nd_address(&one_populated_word)
255            );
256
257            let two_populated_words: HashMap<_, _> = [
258                (bfe!(address), BFieldElement::new(42)),
259                (bfe!(address + 1), BFieldElement::new(42)),
260            ]
261            .into_iter()
262            .collect();
263            assert_eq!(
264                Some(bfe!(address + 2)),
265                first_free_nd_address(&two_populated_words)
266            );
267        }
268
269        let last_word_populated: HashMap<_, _> = [(
270            LAST_ADDRESS_AVAILABLE_FOR_NON_DETERMINISTICALLY_ALLOCATED_MEMORY,
271            bfe!(42),
272        )]
273        .into_iter()
274        .collect();
275        assert!(first_free_nd_address(&last_word_populated).is_none());
276    }
277
278    #[macro_rules_attr::apply(proptest)]
279    fn all_addresses_between_0_and_u32max_belong_to_nd_memory(nd_address: u32) {
280        prop_assert!(nd_memory_region().contains_address(bfe!(nd_address)));
281    }
282
283    #[macro_rules_attr::apply(proptest)]
284    fn addresses_outside_u32_range_do_not_belong_to_nd_memory(
285        #[strategy(arb())]
286        #[filter(#address.value() > u32::MAX as u64)]
287        address: BFieldElement,
288    ) {
289        prop_assert!(!nd_memory_region().contains_address(address));
290    }
291}