|
| 1 | +use p3_field::PrimeField64; |
| 2 | + |
| 3 | +use super::{address::MemoryAddress, error::MemoryError, mem::Memory, val::MemoryValue}; |
| 4 | + |
| 5 | +/// A high level manager for the memory. |
| 6 | +#[derive(Debug, Default)] |
| 7 | +pub struct MemoryManager { |
| 8 | + pub memory: Memory, |
| 9 | +} |
| 10 | + |
| 11 | +impl MemoryManager { |
| 12 | + /// Returns the number of currently allocated segments in memory. |
| 13 | + /// |
| 14 | + /// This reflects the actual physical segment count, starting from segment index 0. |
| 15 | + /// |
| 16 | + /// # Returns |
| 17 | + /// * `usize` — the number of segments allocated in `self.memory`. |
| 18 | + #[must_use] |
| 19 | + pub fn num_segments(&self) -> usize { |
| 20 | + self.memory.data.len() |
| 21 | + } |
| 22 | + |
| 23 | + /// Adds a new, empty segment to memory and returns its starting address. |
| 24 | + /// |
| 25 | + /// This operation appends an empty segment to the memory, which starts at offset 0. |
| 26 | + /// The returned `MemoryAddress` corresponds to the beginning of that segment. |
| 27 | + /// |
| 28 | + /// # Returns |
| 29 | + /// * `MemoryAddress` — the starting address (segment_index, offset=0) of the new segment. |
| 30 | + pub fn add(&mut self) -> MemoryAddress { |
| 31 | + // Allocate a new, empty segment at the end of the list. |
| 32 | + self.memory.data.push(Vec::new()); |
| 33 | + |
| 34 | + // Compute the index of the newly created segment. |
| 35 | + let new_segment_index = self.memory.data.len() - 1; |
| 36 | + |
| 37 | + // Return the starting address of the new segment (offset always 0). |
| 38 | + MemoryAddress { |
| 39 | + segment_index: new_segment_index, |
| 40 | + offset: 0, |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + /// Loads a slice of data into memory starting from a given address. |
| 45 | + /// |
| 46 | + /// The function writes each value in `data` to consecutive addresses starting |
| 47 | + /// from `ptr`. The write is done in reverse order to ensure that any required |
| 48 | + /// memory extension happens once at the end rather than multiple times. |
| 49 | + /// |
| 50 | + /// If all values are written successfully, the function returns the first |
| 51 | + /// address **after** the last inserted value. |
| 52 | + /// |
| 53 | + /// # Type Parameters |
| 54 | + /// * `F`: A finite field, used as the scalar type. |
| 55 | + /// |
| 56 | + /// # Arguments |
| 57 | + /// * `ptr`: Starting address where the data should be written. |
| 58 | + /// * `data`: A slice of memory values representing the values to be stored. |
| 59 | + /// |
| 60 | + /// # Returns |
| 61 | + /// * `Ok(MemoryAddress)` — the address immediately following the last written value. |
| 62 | + /// * `Err(MemoryError<F>)` — if writing fails due to: |
| 63 | + /// - Memory cell already initialized with a different value. |
| 64 | + /// - Overflow when computing addresses. |
| 65 | + /// - Exceeding vector capacity. |
| 66 | + pub fn load_data<F>( |
| 67 | + &mut self, |
| 68 | + ptr: MemoryAddress, |
| 69 | + data: &[MemoryValue<F>], |
| 70 | + ) -> Result<MemoryAddress, MemoryError<F>> |
| 71 | + where |
| 72 | + F: PrimeField64, |
| 73 | + { |
| 74 | + // Iterate over the data values in reverse order, with indices. |
| 75 | + // |
| 76 | + // This reverse order allows any required memory segment resizing |
| 77 | + // (e.g., length extension or capacity reservation) to occur *once* |
| 78 | + // at the highest offset instead of repeatedly during writes. |
| 79 | + for (num, value) in data.iter().enumerate().rev() { |
| 80 | + // Compute the target address: ptr + num. |
| 81 | + // |
| 82 | + // This operation may fail if it causes overflow. |
| 83 | + let addr = (ptr + num).map_err(MemoryError::Math)?; |
| 84 | + |
| 85 | + // Attempt to write the value into memory at the computed address. |
| 86 | + // |
| 87 | + // This enforces the write-once rule — it will fail if the cell is already |
| 88 | + // initialized with a different value. |
| 89 | + self.memory.insert(addr, value.clone())?; |
| 90 | + } |
| 91 | + |
| 92 | + // After writing all values, compute and return the address after the last item. |
| 93 | + // |
| 94 | + // This is simply ptr + data.len(), and it may also fail on overflow. |
| 95 | + (ptr + data.len()).map_err(MemoryError::Math) |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +#[cfg(test)] |
| 100 | +mod tests { |
| 101 | + use p3_baby_bear::BabyBear; |
| 102 | + use p3_field::PrimeCharacteristicRing; |
| 103 | + |
| 104 | + use super::*; |
| 105 | + use crate::{memory::cell::MemoryCell, types::math_errors::MathError}; |
| 106 | + |
| 107 | + type F = BabyBear; |
| 108 | + |
| 109 | + #[test] |
| 110 | + fn test_add_segment_returns_correct_address() { |
| 111 | + // Create a new empty memory manager. |
| 112 | + let mut manager = MemoryManager::default(); |
| 113 | + |
| 114 | + // Initially, there should be no segments. |
| 115 | + assert_eq!(manager.num_segments(), 0); |
| 116 | + |
| 117 | + // Add the first memory segment. |
| 118 | + let addr1 = manager.add(); |
| 119 | + |
| 120 | + // The first segment should have index 0, starting at offset 0. |
| 121 | + assert_eq!(addr1.segment_index, 0); |
| 122 | + assert_eq!(addr1.offset, 0); |
| 123 | + |
| 124 | + // After adding, the total number of segments should be 1. |
| 125 | + assert_eq!(manager.num_segments(), 1); |
| 126 | + |
| 127 | + // Add another segment. |
| 128 | + let addr2 = manager.add(); |
| 129 | + |
| 130 | + // The second segment should have index 1, also starting at offset 0. |
| 131 | + assert_eq!(addr2.segment_index, 1); |
| 132 | + assert_eq!(addr2.offset, 0); |
| 133 | + |
| 134 | + // Now there should be exactly 2 segments. |
| 135 | + assert_eq!(manager.num_segments(), 2); |
| 136 | + } |
| 137 | + |
| 138 | + #[test] |
| 139 | + fn test_load_data_successful() { |
| 140 | + // Create a new memory manager. |
| 141 | + let mut manager = MemoryManager::default(); |
| 142 | + |
| 143 | + // Add a memory segment and get its starting address. |
| 144 | + let base_addr = manager.add(); // segment_index = 0, offset = 0 |
| 145 | + |
| 146 | + // Prepare a list of memory values to load. |
| 147 | + let values = vec![ |
| 148 | + MemoryValue::Int(F::from_u64(10)), |
| 149 | + MemoryValue::Int(F::from_u64(20)), |
| 150 | + MemoryValue::Int(F::from_u64(30)), |
| 151 | + ]; |
| 152 | + |
| 153 | + // Load the data into memory starting at base_addr. |
| 154 | + let end_addr = manager.load_data(base_addr, &values).unwrap(); |
| 155 | + |
| 156 | + // The returned end address should be immediately after the last inserted value. |
| 157 | + assert_eq!(end_addr.segment_index, base_addr.segment_index); |
| 158 | + assert_eq!(end_addr.offset, base_addr.offset + values.len()); |
| 159 | + |
| 160 | + // Verify that each value was inserted correctly at its expected offset. |
| 161 | + for (i, expected) in values.iter().enumerate() { |
| 162 | + let addr = MemoryAddress { |
| 163 | + segment_index: base_addr.segment_index, |
| 164 | + offset: base_addr.offset + i, |
| 165 | + }; |
| 166 | + assert_eq!(manager.memory.get(addr), Some(expected.clone())); |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + #[test] |
| 171 | + fn test_load_data_returns_math_error_on_address_overflow() { |
| 172 | + // Create a new memory manager. |
| 173 | + let mut manager = MemoryManager::default(); |
| 174 | + |
| 175 | + // Define a starting address where the offset is at the maximum `usize` value. |
| 176 | + let base_addr = MemoryAddress { |
| 177 | + segment_index: 0, |
| 178 | + offset: usize::MAX, |
| 179 | + }; |
| 180 | + |
| 181 | + // Manually push an empty segment to allow writing into segment 0. |
| 182 | + manager.memory.data.push(Vec::new()); |
| 183 | + |
| 184 | + // Create two values to write: this will cause an overflow. |
| 185 | + let values = vec![ |
| 186 | + MemoryValue::Int(F::from_u64(1)), |
| 187 | + MemoryValue::Int(F::from_u64(2)), |
| 188 | + ]; |
| 189 | + |
| 190 | + // Try to load data starting at MAX offset. |
| 191 | + // |
| 192 | + // This should fail with an overflow error. |
| 193 | + let err = manager.load_data(base_addr, &values).unwrap_err(); |
| 194 | + |
| 195 | + // Confirm that the error is a MathError due to offset overflow. |
| 196 | + match err { |
| 197 | + MemoryError::Math(MathError::MemoryAddressAddUsizeOffsetExceeded(boxed)) => { |
| 198 | + let (addr, delta) = *boxed; |
| 199 | + // original address |
| 200 | + assert_eq!(addr, base_addr); |
| 201 | + // the amount that caused overflow |
| 202 | + assert_eq!(delta, 1); |
| 203 | + } |
| 204 | + other => panic!("Unexpected error: {other:?}"), |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + #[test] |
| 209 | + fn test_load_data_partial_write_does_not_corrupt_memory() { |
| 210 | + // Create a new memory manager. |
| 211 | + let mut manager = MemoryManager::default(); |
| 212 | + |
| 213 | + // Add a segment (segment_index = 0). |
| 214 | + let _ = manager.add(); |
| 215 | + |
| 216 | + // Construct values to insert. The second will cause a failure. |
| 217 | + let values = vec![ |
| 218 | + MemoryValue::Int(F::from_u64(1)), |
| 219 | + MemoryValue::Int(F::from_u64(2)), |
| 220 | + ]; |
| 221 | + |
| 222 | + // Set the starting address such that adding even one to it will cause overflow. |
| 223 | + let failing_addr = MemoryAddress { |
| 224 | + segment_index: 0, |
| 225 | + offset: usize::MAX - 1, |
| 226 | + }; |
| 227 | + |
| 228 | + // Simulate a memory segment with a small preallocated length. |
| 229 | + manager.memory.data[0] = vec![MemoryCell::NONE; 4]; |
| 230 | + |
| 231 | + // Attempt to load the data starting at the failing address. |
| 232 | + // This should fail due to memory limitations. |
| 233 | + let err = manager.load_data(failing_addr, &values).unwrap_err(); |
| 234 | + |
| 235 | + // Confirm the error is due to exceeding vector capacity. |
| 236 | + assert!(matches!(err, MemoryError::VecCapacityExceeded)); |
| 237 | + } |
| 238 | +} |
0 commit comments