brillig_vm/
memory.rs

1//! Implementation of the VM's memory.
2//!
3//! # Memory Addressing Limits
4//!
5//! The VM uses u32 addresses, theoretically allowing up to 2^32 memory slots. However,
6//! practical limits apply:
7//!
8//! - **Rust allocator limit**: All allocations are capped at `isize::MAX` bytes. On 32-bit
9//!   systems, this limits addressable memory to approximately `i32::MAX / sizeof(MemoryValue)`
10//!   elements (~44 million with typical element sizes).
11//!
12//! - **RAM limit**: On 64-bit systems, the allocator limit is not a concern, but allocating
13//!   the full u32 address space would require ~200 GB of RAM.
14use acir::{
15    AcirField,
16    brillig::{BitSize, IntegerBitSize, MemoryAddress},
17};
18
19use crate::assert_usize;
20
21/// The bit size used for addressing memory within the Brillig VM.
22///
23/// All memory pointers are interpreted as `u32` values, meaning the VM can directly address up to 2^32 memory slots.
24pub const MEMORY_ADDRESSING_BIT_SIZE: IntegerBitSize = IntegerBitSize::U32;
25
26/// Maximum number of memory slots that can be allocated.
27///
28/// This limit is set to `i32::MAX` to ensure deterministic behavior across all architectures.
29/// On 32-bit systems, Rust's allocator limits allocations to `isize::MAX` bytes, which would
30/// restrict us to fewer elements anyway. By using `i32::MAX`, we ensure the same behavior
31/// on both 32-bit and 64-bit systems.
32///
33/// See: <https://github.com/rust-lang/rust/pull/95295> and <https://doc.rust-lang.org/1.81.0/src/core/alloc/layout.rs.html>
34pub const MAX_MEMORY_SIZE: usize = i32::MAX as usize;
35
36/// The current stack pointer is always in slot 0.
37///
38/// It gets manipulated by opcodes laid down for calls by codegen.
39pub const STACK_POINTER_ADDRESS: MemoryAddress = MemoryAddress::Direct(0);
40
41/// The _free memory pointer_ is always in slot 1.
42///
43/// We added it here to be able to implement a workaround for wrapping around
44/// the free memory, ie. to detect "out of memory" events, but the AVM is not,
45/// and does not want to be aware of the _free memory pointer_, so we cannot,
46/// in general, build much functionality in the VM around it.
47pub const FREE_MEMORY_POINTER_ADDRESS: MemoryAddress = MemoryAddress::Direct(1);
48
49/// Offset constants for arrays and vectors:
50/// * Arrays are `[ref-count, ...items]`
51/// * Vectors are `[ref-count, size, capacity, ...items]`
52pub mod offsets {
53    /// Number of prefix fields in an array: RC.
54    pub const ARRAY_META_COUNT: u32 = 1;
55    pub const ARRAY_ITEMS: u32 = 1;
56
57    /// Number of prefix fields in a vector: RC, size, capacity.
58    pub const VECTOR_META_COUNT: u32 = 3;
59    pub const VECTOR_SIZE: u32 = 1;
60    pub const VECTOR_CAPACITY: u32 = 2;
61    pub const VECTOR_ITEMS: u32 = 3;
62}
63
64/// Wrapper for array addresses, with convenience methods for various offsets.
65///
66/// The array consists of a ref-count followed by a number of items according
67/// the size indicated by the type.
68pub(crate) struct ArrayAddress(MemoryAddress);
69
70impl ArrayAddress {
71    /// The start of the items, after the meta-data.
72    pub(crate) fn items_start(&self) -> MemoryAddress {
73        self.0.offset(offsets::ARRAY_ITEMS)
74    }
75}
76
77impl From<MemoryAddress> for ArrayAddress {
78    fn from(value: MemoryAddress) -> Self {
79        Self(value)
80    }
81}
82
83/// A single typed value in the Brillig VM's memory.
84///
85/// Memory in the VM is strongly typed and can represent either a native field element
86/// or an integer of a specific bit width. This enum encapsulates all supported
87/// in-memory types and allows conversion between representations.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
89pub enum MemoryValue<F> {
90    Field(F),
91    U1(bool),
92    U8(u8),
93    U16(u16),
94    U32(u32),
95    U64(u64),
96    U128(u128),
97}
98
99/// Represents errors that can occur when interpreting or converting typed memory values.
100#[derive(Debug, thiserror::Error)]
101pub enum MemoryTypeError {
102    /// The value's bit size does not match the expected bit size for the operation.
103    #[error(
104        "Bit size for value {value_bit_size} does not match the expected bit size {expected_bit_size}"
105    )]
106    MismatchedBitSize { value_bit_size: u32, expected_bit_size: u32 },
107    /// The memory value is not an integer and cannot be interpreted as one.
108    /// For example, this can be triggered when attempting to convert a field element to an integer such as in [`MemoryValue::to_u128`].
109    #[error("Value is not an integer")]
110    NotAnInteger,
111}
112
113impl<F: std::fmt::Display> MemoryValue<F> {
114    /// Builds a field-typed memory value.
115    pub fn new_field(value: F) -> Self {
116        MemoryValue::Field(value)
117    }
118
119    /// Builds an integer-typed memory value.
120    pub fn new_integer(value: u128, bit_size: IntegerBitSize) -> Self {
121        match bit_size {
122            IntegerBitSize::U1 => MemoryValue::U1(match value {
123                0 => false,
124                1 => true,
125                _ => panic!("{value} is out of 1 bit range"),
126            }),
127            IntegerBitSize::U8 => {
128                MemoryValue::U8(value.try_into().expect("{value} is out of 8 bits range"))
129            }
130            IntegerBitSize::U16 => {
131                MemoryValue::U16(value.try_into().expect("{value} is out of 16 bits range"))
132            }
133            IntegerBitSize::U32 => {
134                MemoryValue::U32(value.try_into().expect("{value} is out of 32 bits range"))
135            }
136            IntegerBitSize::U64 => {
137                MemoryValue::U64(value.try_into().expect("{value} is out of 64 bits range"))
138            }
139            IntegerBitSize::U128 => MemoryValue::U128(value),
140        }
141    }
142
143    pub fn bit_size(&self) -> BitSize {
144        match self {
145            MemoryValue::Field(_) => BitSize::Field,
146            MemoryValue::U1(_) => BitSize::Integer(IntegerBitSize::U1),
147            MemoryValue::U8(_) => BitSize::Integer(IntegerBitSize::U8),
148            MemoryValue::U16(_) => BitSize::Integer(IntegerBitSize::U16),
149            MemoryValue::U32(_) => BitSize::Integer(IntegerBitSize::U32),
150            MemoryValue::U64(_) => BitSize::Integer(IntegerBitSize::U64),
151            MemoryValue::U128(_) => BitSize::Integer(IntegerBitSize::U128),
152        }
153    }
154
155    /// Expects a `U32` value and converts it into `usize`, otherwise panics.
156    ///
157    /// Primarily a convenience method for using values in memory operations as pointers, sizes and offsets.
158    pub fn to_u32(&self) -> u32 {
159        match self {
160            MemoryValue::U32(value) => *value,
161            other => panic!("value is not typed as Brillig usize: {other}"),
162        }
163    }
164}
165
166impl<F: AcirField> MemoryValue<F> {
167    /// Builds a memory value from a field element, either field or integer type.
168    ///
169    /// If the bit size indicates an integer type, the value is downcast to fit into the specified size.
170    pub fn new_from_field(value: F, bit_size: BitSize) -> Self {
171        if let BitSize::Integer(bit_size) = bit_size {
172            MemoryValue::new_integer(value.to_u128(), bit_size)
173        } else {
174            MemoryValue::new_field(value)
175        }
176    }
177
178    /// Builds a memory value from a field element, checking that the value is within the bit size,
179    /// otherwise returns `None`.
180    pub fn new_checked(value: F, bit_size: BitSize) -> Option<Self> {
181        if let BitSize::Integer(bit_size) = bit_size
182            && value.num_bits() > bit_size.into()
183        {
184            return None;
185        }
186
187        Some(MemoryValue::new_from_field(value, bit_size))
188    }
189
190    /// Converts the memory value to a field element, independent of its type.
191    pub fn to_field(&self) -> F {
192        match self {
193            MemoryValue::Field(value) => *value,
194            MemoryValue::U1(value) => F::from(*value),
195            MemoryValue::U8(value) => F::from(u128::from(*value)),
196            MemoryValue::U16(value) => F::from(u128::from(*value)),
197            MemoryValue::U32(value) => F::from(u128::from(*value)),
198            MemoryValue::U64(value) => F::from(u128::from(*value)),
199            MemoryValue::U128(value) => F::from(*value),
200        }
201    }
202
203    /// Converts the memory value to U128, if the value is an integer.
204    pub fn to_u128(&self) -> Result<u128, MemoryTypeError> {
205        match self {
206            MemoryValue::Field(..) => Err(MemoryTypeError::NotAnInteger),
207            MemoryValue::U1(value) => Ok(u128::from(*value)),
208            MemoryValue::U8(value) => Ok(u128::from(*value)),
209            MemoryValue::U16(value) => Ok(u128::from(*value)),
210            MemoryValue::U32(value) => Ok(u128::from(*value)),
211            MemoryValue::U64(value) => Ok(u128::from(*value)),
212            MemoryValue::U128(value) => Ok(*value),
213        }
214    }
215
216    /// Extracts the field element from the memory value, if it is typed as field element.
217    pub fn expect_field(self) -> Result<F, MemoryTypeError> {
218        if let MemoryValue::Field(field) = self {
219            Ok(field)
220        } else {
221            Err(MemoryTypeError::MismatchedBitSize {
222                value_bit_size: self.bit_size().to_u32::<F>(),
223                expected_bit_size: F::max_num_bits(),
224            })
225        }
226    }
227    pub(crate) fn expect_u1(self) -> Result<bool, MemoryTypeError> {
228        if let MemoryValue::U1(value) = self {
229            Ok(value)
230        } else {
231            Err(MemoryTypeError::MismatchedBitSize {
232                value_bit_size: self.bit_size().to_u32::<F>(),
233                expected_bit_size: 1,
234            })
235        }
236    }
237
238    pub(crate) fn expect_u8(self) -> Result<u8, MemoryTypeError> {
239        if let MemoryValue::U8(value) = self {
240            Ok(value)
241        } else {
242            Err(MemoryTypeError::MismatchedBitSize {
243                value_bit_size: self.bit_size().to_u32::<F>(),
244                expected_bit_size: 8,
245            })
246        }
247    }
248
249    pub(crate) fn expect_u16(self) -> Result<u16, MemoryTypeError> {
250        if let MemoryValue::U16(value) = self {
251            Ok(value)
252        } else {
253            Err(MemoryTypeError::MismatchedBitSize {
254                value_bit_size: self.bit_size().to_u32::<F>(),
255                expected_bit_size: 16,
256            })
257        }
258    }
259
260    pub(crate) fn expect_u32(self) -> Result<u32, MemoryTypeError> {
261        if let MemoryValue::U32(value) = self {
262            Ok(value)
263        } else {
264            Err(MemoryTypeError::MismatchedBitSize {
265                value_bit_size: self.bit_size().to_u32::<F>(),
266                expected_bit_size: 32,
267            })
268        }
269    }
270
271    pub(crate) fn expect_u64(self) -> Result<u64, MemoryTypeError> {
272        if let MemoryValue::U64(value) = self {
273            Ok(value)
274        } else {
275            Err(MemoryTypeError::MismatchedBitSize {
276                value_bit_size: self.bit_size().to_u32::<F>(),
277                expected_bit_size: 64,
278            })
279        }
280    }
281
282    pub(crate) fn expect_u128(self) -> Result<u128, MemoryTypeError> {
283        if let MemoryValue::U128(value) = self {
284            Ok(value)
285        } else {
286            Err(MemoryTypeError::MismatchedBitSize {
287                value_bit_size: self.bit_size().to_u32::<F>(),
288                expected_bit_size: 128,
289            })
290        }
291    }
292}
293
294impl<F: std::fmt::Display> std::fmt::Display for MemoryValue<F> {
295    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
296        match self {
297            MemoryValue::Field(value) => write!(f, "{value}: field"),
298            MemoryValue::U1(value) => write!(f, "{value}: u1"),
299            MemoryValue::U8(value) => write!(f, "{value}: u8"),
300            MemoryValue::U16(value) => write!(f, "{value}: u16"),
301            MemoryValue::U32(value) => write!(f, "{value}: u32"),
302            MemoryValue::U64(value) => write!(f, "{value}: u64"),
303            MemoryValue::U128(value) => write!(f, "{value}: u128"),
304        }
305    }
306}
307
308impl<F: AcirField> Default for MemoryValue<F> {
309    fn default() -> Self {
310        MemoryValue::new_field(F::zero())
311    }
312}
313
314impl<F: AcirField> From<bool> for MemoryValue<F> {
315    fn from(value: bool) -> Self {
316        MemoryValue::U1(value)
317    }
318}
319
320impl<F: AcirField> From<u8> for MemoryValue<F> {
321    fn from(value: u8) -> Self {
322        MemoryValue::U8(value)
323    }
324}
325
326impl<F: AcirField> From<u32> for MemoryValue<F> {
327    fn from(value: u32) -> Self {
328        MemoryValue::U32(value)
329    }
330}
331
332impl<F: AcirField> From<u64> for MemoryValue<F> {
333    fn from(value: u64) -> Self {
334        MemoryValue::U64(value)
335    }
336}
337
338impl<F: AcirField> From<u128> for MemoryValue<F> {
339    fn from(value: u128) -> Self {
340        MemoryValue::U128(value)
341    }
342}
343
344impl<F: AcirField> TryFrom<MemoryValue<F>> for bool {
345    type Error = MemoryTypeError;
346
347    fn try_from(memory_value: MemoryValue<F>) -> Result<Self, Self::Error> {
348        memory_value.expect_u1()
349    }
350}
351
352impl<F: AcirField> TryFrom<MemoryValue<F>> for u8 {
353    type Error = MemoryTypeError;
354
355    fn try_from(memory_value: MemoryValue<F>) -> Result<Self, Self::Error> {
356        memory_value.expect_u8()
357    }
358}
359
360impl<F: AcirField> TryFrom<MemoryValue<F>> for u32 {
361    type Error = MemoryTypeError;
362
363    fn try_from(memory_value: MemoryValue<F>) -> Result<Self, Self::Error> {
364        memory_value.expect_u32()
365    }
366}
367
368impl<F: AcirField> TryFrom<MemoryValue<F>> for u64 {
369    type Error = MemoryTypeError;
370
371    fn try_from(memory_value: MemoryValue<F>) -> Result<Self, Self::Error> {
372        memory_value.expect_u64()
373    }
374}
375
376impl<F: AcirField> TryFrom<MemoryValue<F>> for u128 {
377    type Error = MemoryTypeError;
378
379    fn try_from(memory_value: MemoryValue<F>) -> Result<Self, Self::Error> {
380        memory_value.expect_u128()
381    }
382}
383/// The VM's memory.
384///
385/// Memory is internally represented as a vector of values.
386/// We grow the memory when values past the end are set, extending with 0s.
387///
388/// # Capacity Limits
389///
390/// The inner `Vec` is subject to Rust's allocator limit of `isize::MAX` bytes.
391/// This means:
392/// - On 64-bit: Practical limit is available RAM (~200 GB for full u32 range)
393/// - On 32-bit: Hard limit of ~44 million addressable slots
394///
395/// Exceeding these limits will cause a panic with "capacity overflow".
396#[derive(Debug, Clone, PartialEq, Eq)]
397pub struct Memory<F> {
398    // Internal memory representation
399    inner: Vec<MemoryValue<F>>,
400    /// Cached stack pointer to avoid a memory read + enum match on every
401    /// relative address resolution.
402    ///
403    /// The canonical value lives in memory slot [`STACK_POINTER_ADDRESS`]
404    /// and must remain there for downstream ZK VM proving. We mirror it here
405    /// because [`Self::resolve`] is called on every memory access
406    /// with a relative address. Updated on writes to slot [`STACK_POINTER_ADDRESS`]
407    /// which are more rare than reads.
408    stack_pointer: u32,
409}
410
411impl<F> Default for Memory<F> {
412    fn default() -> Self {
413        Self { inner: Vec::new(), stack_pointer: STACK_POINTER_ADDRESS.to_u32() }
414    }
415}
416
417impl<F: AcirField> Memory<F> {
418    /// Resolve an address to either:
419    /// * itself, if it's a direct address, or
420    /// * the cached stack pointer plus the offset, if it's relative.
421    ///
422    /// Returns a memory slot index.
423    fn resolve(&self, address: MemoryAddress) -> u32 {
424        match address {
425            MemoryAddress::Direct(address) => address,
426            MemoryAddress::Relative(offset) => {
427                self.stack_pointer.checked_add(offset).expect("stack pointer offset overflow")
428            }
429        }
430    }
431
432    /// Reads the numeric value at the address.
433    ///
434    /// If the address is beyond the size of memory, a default value is returned.
435    pub fn read(&self, address: MemoryAddress) -> MemoryValue<F> {
436        let resolved_addr = assert_usize(self.resolve(address));
437        self.inner.get(resolved_addr).copied().unwrap_or_default()
438    }
439
440    /// Reads the value at the address and returns it as a direct memory address,
441    /// without dereferencing the pointer itself to a numeric value.
442    pub fn read_ref(&self, ptr: MemoryAddress) -> MemoryAddress {
443        let resolved = assert_usize(self.resolve(ptr));
444        if resolved >= self.inner.len() {
445            panic!(
446                "read_ref: address {ptr:?} (resolved to {resolved}) is out of bounds (memory size: {})",
447                self.inner.len()
448            );
449        }
450        let value = self.inner[resolved];
451        let MemoryValue::U32(addr) = value else {
452            panic!(
453                "read_ref: expected a U32 pointer at address {ptr:?}, but found {value} ({})",
454                value.bit_size()
455            );
456        };
457        MemoryAddress::direct(addr)
458    }
459
460    /// Sets `ptr` to point at `address`.
461    pub fn write_ref(&mut self, ptr: MemoryAddress, address: MemoryAddress) {
462        self.write(ptr, MemoryValue::from(address.to_u32()));
463    }
464
465    /// Read a contiguous vector of memory starting at `address`, up to `len` slots.
466    ///
467    /// Panics if the end index is beyond the size of the memory.
468    pub fn read_slice(&self, address: MemoryAddress, len: usize) -> &[MemoryValue<F>] {
469        // Allows to read a vector of uninitialized memory if the length is zero.
470        // Ideally we'd be able to read uninitialized memory in general (as read does)
471        // but that's not possible if we want to return a vector instead of owned data.
472        if len == 0 {
473            return &[];
474        }
475        let resolved_addr = assert_usize(self.resolve(address));
476        let end = resolved_addr.checked_add(len).expect("read_slice: address + len overflows");
477        assert!(
478            end <= self.inner.len(),
479            "read_slice: out of bounds — reading {len} elements from address {resolved_addr} \
480             exceeds memory size {}. Callers should validate sizes before calling read_slice.",
481            self.inner.len()
482        );
483        &self.inner[resolved_addr..end]
484    }
485
486    /// Sets the value at `address` to `value`
487    pub fn write(&mut self, address: MemoryAddress, value: MemoryValue<F>) {
488        let resolved_addr = assert_usize(self.resolve(address));
489        // Saturate avoids errors, and leave them to `resize_to_fit`
490        self.resize_to_fit(resolved_addr.saturating_add(1));
491        self.inner[resolved_addr] = value;
492        if address == STACK_POINTER_ADDRESS
493            && let MemoryValue::U32(sp) = value
494        {
495            self.stack_pointer = sp;
496        }
497    }
498
499    /// Increase the size of memory fit `size` elements, or the current length, whichever is bigger.
500    ///
501    /// # Panics
502    ///
503    /// Panics if `size` exceeds [`MAX_MEMORY_SIZE`].
504    fn resize_to_fit(&mut self, size: usize) {
505        assert!(
506            size <= MAX_MEMORY_SIZE,
507            "Memory address space exceeded: requested {size} slots, maximum is {MAX_MEMORY_SIZE} (i32::MAX)"
508        );
509        // Calculate new memory size
510        let new_size = std::cmp::max(self.inner.len(), size);
511        // Expand memory to new size with default values if needed
512        self.inner.resize(new_size, MemoryValue::default());
513    }
514
515    /// Sets the values after `address` to `values`
516    pub fn write_slice(&mut self, address: MemoryAddress, values: &[MemoryValue<F>]) {
517        let resolved_addr = assert_usize(self.resolve(address));
518        let end_addr = resolved_addr + values.len();
519        self.resize_to_fit(end_addr);
520        self.inner[resolved_addr..end_addr].copy_from_slice(values);
521        if address == STACK_POINTER_ADDRESS
522            && let Some(MemoryValue::U32(sp)) = values.first()
523        {
524            self.stack_pointer = *sp;
525        }
526    }
527
528    /// Returns the number of memory slots currently allocated.
529    pub(crate) fn len(&self) -> usize {
530        self.inner.len()
531    }
532
533    /// Returns the values of the memory
534    pub fn values(&self) -> &[MemoryValue<F>] {
535        &self.inner
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542    use acir::FieldElement;
543    use test_case::test_case;
544
545    #[test]
546    fn direct_write_and_read() {
547        let mut memory = Memory::<FieldElement>::default();
548        let addr = MemoryAddress::direct(5);
549
550        memory.write(addr, MemoryValue::U32(42));
551        assert_eq!(memory.read(addr).to_u128().unwrap(), 42);
552    }
553
554    #[test]
555    fn relative_write_and_read() {
556        let mut memory = Memory::<FieldElement>::default();
557        // Stack pointer = 10
558        memory.write(MemoryAddress::direct(0), MemoryValue::U32(10));
559
560        let addr = MemoryAddress::Relative(5);
561        memory.write(addr, MemoryValue::U32(42));
562        assert_eq!(memory.read(addr).to_u128().unwrap(), 42);
563
564        let resolved_addr = memory.resolve(addr);
565        // Stack pointer + offset
566        // 10 + 5 = 15
567        assert_eq!(resolved_addr, 15);
568        assert_eq!(memory.values()[assert_usize(resolved_addr)].to_u128().unwrap(), 42);
569    }
570
571    #[test]
572    fn memory_growth() {
573        let mut memory = Memory::<FieldElement>::default();
574        let addr = MemoryAddress::direct(10);
575
576        memory.write(addr, MemoryValue::U32(123));
577
578        let mut expected = vec![MemoryValue::default(); 10];
579        expected.push(MemoryValue::U32(123));
580
581        assert_eq!(memory.values(), &expected);
582    }
583
584    #[test]
585    fn resize_to_fit_grows_memory() {
586        let mut memory = Memory::<FieldElement>::default();
587        memory.resize_to_fit(15);
588
589        assert_eq!(memory.values().len(), 15);
590        assert!(memory.values().iter().all(|v| *v == MemoryValue::default()));
591    }
592
593    #[test]
594    fn write_and_read_slice() {
595        let mut memory = Memory::<FieldElement>::default();
596        // [1, 2, 3, 4, 5]
597        let values: Vec<_> = (1..=5).map(MemoryValue::U32).collect();
598
599        // Write at an address > 0 to show resizing
600        memory.write_slice(MemoryAddress::direct(2), &values);
601        assert_eq!(
602            memory
603                .read_slice(MemoryAddress::direct(2), 3)
604                .iter()
605                .map(|v| v.to_u128().unwrap())
606                .collect::<Vec<_>>(),
607            vec![1, 2, 3]
608        );
609        assert_eq!(
610            memory
611                .read_slice(MemoryAddress::direct(5), 2)
612                .iter()
613                .map(|v| v.to_u128().unwrap())
614                .collect::<Vec<_>>(),
615            vec![4, 5]
616        );
617        let zero_field = FieldElement::zero();
618        assert_eq!(
619            memory
620                .read_slice(MemoryAddress::direct(0), 2)
621                .iter()
622                .map(|v| v.to_field())
623                .collect::<Vec<_>>(),
624            vec![zero_field, zero_field]
625        );
626        assert_eq!(
627            memory
628                .read_slice(MemoryAddress::direct(2), 5)
629                .iter()
630                .map(|v| v.to_u128().unwrap())
631                .collect::<Vec<_>>(),
632            vec![1, 2, 3, 4, 5]
633        );
634    }
635
636    #[test]
637    fn read_ref_returns_expected_address_and_reads_slice() {
638        let mut memory = Memory::<FieldElement>::default();
639
640        // Imagine we have a heap array starting at address 10
641        let heap_start = MemoryAddress::direct(10);
642        // [1, 2, 3]
643        let values: Vec<_> = (1..=3).map(MemoryValue::U32).collect();
644        memory.write_slice(heap_start, &values);
645
646        let array_pointer = MemoryAddress::direct(1);
647        // Store a pointer to that array at address 1 (after the stack pointer)
648        memory.write(array_pointer, MemoryValue::U32(10));
649
650        // `read_ref` should read that pointer and returns MemoryAddress::direct(10)
651        let array_start = memory.read_ref(array_pointer);
652        assert_eq!(array_start, MemoryAddress::direct(10));
653
654        // Use that reference to read the 3 element array
655        let got_slice = memory.read_slice(array_start, 3);
656        assert_eq!(got_slice, values);
657    }
658
659    #[test]
660    fn zero_length_slice() {
661        let memory = Memory::<FieldElement>::default();
662        assert_eq!(memory.read_slice(MemoryAddress::direct(20), 0), &[]);
663    }
664
665    #[test]
666    fn read_from_non_existent_memory() {
667        let memory = Memory::<FieldElement>::default();
668        let result = memory.read(MemoryAddress::direct(20));
669        // `Memory::read` returns zero at out of bounds indices
670        assert!(result.to_field().is_zero());
671    }
672
673    #[test]
674    #[should_panic(expected = "read_slice: out of bounds")]
675    fn read_vector_from_non_existent_memory() {
676        let memory = Memory::<FieldElement>::default();
677        let _ = memory.read_slice(MemoryAddress::direct(20), 10);
678    }
679
680    #[test]
681    #[should_panic(expected = "Memory address space exceeded")]
682    fn resize_to_fit_panics_when_exceeding_max_memory_size() {
683        let mut memory = Memory::<FieldElement>::default();
684        // Attempting to resize beyond i32::MAX should panic
685        memory.resize_to_fit(MAX_MEMORY_SIZE + 1);
686    }
687
688    #[test_case(IntegerBitSize::U1, 2)]
689    #[test_case(IntegerBitSize::U8, 256)]
690    #[test_case(IntegerBitSize::U16, u128::from(u16::MAX) + 1)]
691    #[test_case(IntegerBitSize::U32, u128::from(u32::MAX) + 1)]
692    #[test_case(IntegerBitSize::U64, u128::from(u64::MAX) + 1)]
693    #[should_panic(expected = "range")]
694    fn memory_value_new_integer_out_of_range(bit_size: IntegerBitSize, value: u128) {
695        let _ = MemoryValue::<FieldElement>::new_integer(value, bit_size);
696    }
697
698    #[test]
699    #[should_panic = "stack pointer offset overflow"]
700    fn memory_resolve_overflow() {
701        let mut memory = Memory::<FieldElement>::default();
702        memory.write(STACK_POINTER_ADDRESS, MemoryValue::from(u32::MAX - 10));
703        let addr = MemoryAddress::relative(20);
704        let _wrap = memory.resolve(addr);
705    }
706}