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