openvm_circuit/system/memory/
offline.rs

1use std::{array, cmp::max};
2
3use openvm_circuit_primitives::{
4    assert_less_than::AssertLtSubAir, var_range::SharedVariableRangeCheckerChip,
5};
6use openvm_stark_backend::p3_field::PrimeField32;
7use rustc_hash::FxHashSet;
8
9use super::{AddressMap, PagedVec, PAGE_SIZE};
10use crate::{
11    arch::MemoryConfig,
12    system::memory::{
13        adapter::{AccessAdapterInventory, AccessAdapterRecord, AccessAdapterRecordKind},
14        offline_checker::{MemoryBridge, MemoryBus},
15        MemoryAuxColsFactory, MemoryImage, RecordId, TimestampedEquipartition, TimestampedValues,
16    },
17};
18
19pub const INITIAL_TIMESTAMP: u32 = 0;
20
21#[repr(C)]
22#[derive(Clone, Default, PartialEq, Eq, Debug)]
23struct BlockData {
24    pointer: u32,
25    timestamp: u32,
26    size: usize,
27}
28
29struct BlockMap {
30    /// Block ids. 0 is a special value standing for the default block.
31    id: AddressMap<usize, PAGE_SIZE>,
32    /// The place where non-default blocks are stored.
33    storage: Vec<BlockData>,
34    initial_block_size: usize,
35}
36
37impl BlockMap {
38    pub fn from_mem_config(mem_config: &MemoryConfig, initial_block_size: usize) -> Self {
39        assert!(initial_block_size.is_power_of_two());
40        Self {
41            id: AddressMap::from_mem_config(mem_config),
42            storage: vec![],
43            initial_block_size,
44        }
45    }
46
47    fn initial_block_data(pointer: u32, initial_block_size: usize) -> BlockData {
48        let aligned_pointer = (pointer / initial_block_size as u32) * initial_block_size as u32;
49        BlockData {
50            pointer: aligned_pointer,
51            size: initial_block_size,
52            timestamp: INITIAL_TIMESTAMP,
53        }
54    }
55
56    pub fn get_without_adding(&self, address: &(u32, u32)) -> BlockData {
57        let idx = self.id.get(address).unwrap_or(&0);
58        if idx == &0 {
59            Self::initial_block_data(address.1, self.initial_block_size)
60        } else {
61            self.storage[idx - 1].clone()
62        }
63    }
64
65    pub fn get(&mut self, address: &(u32, u32)) -> &BlockData {
66        let (address_space, pointer) = *address;
67        let idx = self.id.get(&(address_space, pointer)).unwrap_or(&0);
68        if idx == &0 {
69            // `initial_block_size` is a power of two, as asserted in `from_mem_config`.
70            let pointer = pointer & !(self.initial_block_size as u32 - 1);
71            self.set_range(
72                &(address_space, pointer),
73                self.initial_block_size,
74                Self::initial_block_data(pointer, self.initial_block_size),
75            );
76            self.storage.last().unwrap()
77        } else {
78            &self.storage[idx - 1]
79        }
80    }
81
82    pub fn get_mut(&mut self, address: &(u32, u32)) -> &mut BlockData {
83        let (address_space, pointer) = *address;
84        let idx = self.id.get(&(address_space, pointer)).unwrap_or(&0);
85        if idx == &0 {
86            let pointer = pointer - pointer % self.initial_block_size as u32;
87            self.set_range(
88                &(address_space, pointer),
89                self.initial_block_size,
90                Self::initial_block_data(pointer, self.initial_block_size),
91            );
92            self.storage.last_mut().unwrap()
93        } else {
94            &mut self.storage[idx - 1]
95        }
96    }
97
98    pub fn set_range(&mut self, address: &(u32, u32), len: usize, block: BlockData) {
99        let (address_space, pointer) = address;
100        self.storage.push(block);
101        for i in 0..len {
102            self.id
103                .insert(&(*address_space, pointer + i as u32), self.storage.len());
104        }
105    }
106
107    pub fn items(&self) -> impl Iterator<Item = ((u32, u32), &BlockData)> + '_ {
108        self.id
109            .items()
110            .filter(|(_, idx)| *idx > 0)
111            .map(|(address, idx)| (address, &self.storage[idx - 1]))
112    }
113}
114
115#[derive(Debug, Clone, PartialEq)]
116pub struct MemoryRecord<T> {
117    pub address_space: T,
118    pub pointer: T,
119    pub timestamp: u32,
120    pub prev_timestamp: u32,
121    data: Vec<T>,
122    /// None if a read.
123    prev_data: Option<Vec<T>>,
124}
125
126impl<T> MemoryRecord<T> {
127    pub fn data_slice(&self) -> &[T] {
128        self.data.as_slice()
129    }
130
131    pub fn prev_data_slice(&self) -> Option<&[T]> {
132        self.prev_data.as_deref()
133    }
134}
135
136impl<T: Copy> MemoryRecord<T> {
137    pub fn data_at(&self, index: usize) -> T {
138        self.data[index]
139    }
140}
141
142pub struct OfflineMemory<F> {
143    block_data: BlockMap,
144    data: Vec<PagedVec<F, PAGE_SIZE>>,
145    as_offset: u32,
146    timestamp: u32,
147    timestamp_max_bits: usize,
148
149    memory_bus: MemoryBus,
150    range_checker: SharedVariableRangeCheckerChip,
151
152    log: Vec<Option<MemoryRecord<F>>>,
153}
154
155impl<F: PrimeField32> OfflineMemory<F> {
156    /// Creates a new partition with the given initial block size.
157    ///
158    /// Panics if the initial block size is not a power of two.
159    pub fn new(
160        initial_memory: MemoryImage<F>,
161        initial_block_size: usize,
162        memory_bus: MemoryBus,
163        range_checker: SharedVariableRangeCheckerChip,
164        config: MemoryConfig,
165    ) -> Self {
166        assert_eq!(initial_memory.as_offset, config.as_offset);
167        Self {
168            block_data: BlockMap::from_mem_config(&config, initial_block_size),
169            data: initial_memory.paged_vecs,
170            as_offset: config.as_offset,
171            timestamp: INITIAL_TIMESTAMP + 1,
172            timestamp_max_bits: config.clk_max_bits,
173            memory_bus,
174            range_checker,
175            log: vec![],
176        }
177    }
178
179    pub fn set_initial_memory(&mut self, initial_memory: MemoryImage<F>, config: MemoryConfig) {
180        assert_eq!(self.timestamp, INITIAL_TIMESTAMP + 1);
181        assert_eq!(initial_memory.as_offset, config.as_offset);
182        self.as_offset = config.as_offset;
183        self.data = initial_memory.paged_vecs;
184    }
185
186    pub(super) fn set_log_capacity(&mut self, access_capacity: usize) {
187        assert!(self.log.is_empty());
188        self.log = Vec::with_capacity(access_capacity);
189    }
190
191    pub fn memory_bridge(&self) -> MemoryBridge {
192        MemoryBridge::new(
193            self.memory_bus,
194            self.timestamp_max_bits,
195            self.range_checker.bus(),
196        )
197    }
198
199    pub fn timestamp(&self) -> u32 {
200        self.timestamp
201    }
202
203    /// Increments the current timestamp by one and returns the new value.
204    pub fn increment_timestamp(&mut self) {
205        self.increment_timestamp_by(1)
206    }
207
208    /// Increments the current timestamp by a specified delta and returns the new value.
209    pub fn increment_timestamp_by(&mut self, delta: u32) {
210        self.log.push(None);
211        self.timestamp += delta;
212    }
213
214    /// Writes an array of values to the memory at the specified address space and start index.
215    pub fn write(
216        &mut self,
217        address_space: u32,
218        pointer: u32,
219        values: Vec<F>,
220        records: &mut AccessAdapterInventory<F>,
221    ) {
222        let len = values.len();
223        assert!(len.is_power_of_two());
224        assert_ne!(address_space, 0);
225
226        let prev_timestamp = self.access_updating_timestamp(address_space, pointer, len, records);
227
228        debug_assert!(prev_timestamp < self.timestamp);
229
230        let pointer = pointer as usize;
231        let prev_data = self.data[(address_space - self.as_offset) as usize]
232            .set_range(pointer..pointer + len, &values);
233
234        let record = MemoryRecord {
235            address_space: F::from_canonical_u32(address_space),
236            pointer: F::from_canonical_usize(pointer),
237            timestamp: self.timestamp,
238            prev_timestamp,
239            data: values,
240            prev_data: Some(prev_data),
241        };
242        self.log.push(Some(record));
243        self.timestamp += 1;
244    }
245
246    /// Reads an array of values from the memory at the specified address space and start index.
247    pub fn read(
248        &mut self,
249        address_space: u32,
250        pointer: u32,
251        len: usize,
252        adapter_records: &mut AccessAdapterInventory<F>,
253    ) {
254        assert!(len.is_power_of_two());
255        if address_space == 0 {
256            let pointer = F::from_canonical_u32(pointer);
257            self.log.push(Some(MemoryRecord {
258                address_space: F::ZERO,
259                pointer,
260                timestamp: self.timestamp,
261                prev_timestamp: 0,
262                data: vec![pointer],
263                prev_data: None,
264            }));
265            self.timestamp += 1;
266            return;
267        }
268
269        let prev_timestamp =
270            self.access_updating_timestamp(address_space, pointer, len, adapter_records);
271
272        debug_assert!(prev_timestamp < self.timestamp);
273
274        let values = self.range_vec(address_space, pointer, len);
275
276        self.log.push(Some(MemoryRecord {
277            address_space: F::from_canonical_u32(address_space),
278            pointer: F::from_canonical_u32(pointer),
279            timestamp: self.timestamp,
280            prev_timestamp,
281            data: values,
282            prev_data: None,
283        }));
284        self.timestamp += 1;
285    }
286
287    pub fn record_by_id(&self, id: RecordId) -> &MemoryRecord<F> {
288        self.log[id.0].as_ref().unwrap()
289    }
290
291    pub fn finalize<const N: usize>(
292        &mut self,
293        adapter_records: &mut AccessAdapterInventory<F>,
294    ) -> TimestampedEquipartition<F, N> {
295        // First make sure the partition we maintain in self.block_data is an equipartition.
296        // Grab all aligned pointers that need to be re-accessed.
297        let to_access: FxHashSet<_> = self
298            .block_data
299            .items()
300            .map(|((address_space, pointer), _)| (address_space, (pointer / N as u32) * N as u32))
301            .collect();
302
303        for &(address_space, pointer) in to_access.iter() {
304            let block = self.block_data.get(&(address_space, pointer));
305            if block.pointer != pointer || block.size != N {
306                self.access(address_space, pointer, N, adapter_records);
307            }
308        }
309
310        let mut equipartition = TimestampedEquipartition::<F, N>::new();
311        for (address_space, pointer) in to_access {
312            let block = self.block_data.get(&(address_space, pointer));
313
314            debug_assert_eq!(block.pointer % N as u32, 0);
315            debug_assert_eq!(block.size, N);
316
317            equipartition.insert(
318                (address_space, pointer / N as u32),
319                TimestampedValues {
320                    timestamp: block.timestamp,
321                    values: self.range_array::<N>(address_space, pointer),
322                },
323            );
324        }
325        equipartition
326    }
327
328    // Modifies the partition to ensure that there is a block starting at (address_space, query).
329    fn split_to_make_boundary(
330        &mut self,
331        address_space: u32,
332        query: u32,
333        records: &mut AccessAdapterInventory<F>,
334    ) {
335        let lim = (self.data[(address_space - self.as_offset) as usize].memory_size()) as u32;
336        if query == lim {
337            return;
338        }
339        assert!(query < lim);
340        let original_block = self.block_containing(address_space, query);
341        if original_block.pointer == query {
342            return;
343        }
344
345        let data = self.range_vec(address_space, original_block.pointer, original_block.size);
346
347        let timestamp = original_block.timestamp;
348
349        let mut cur_ptr = original_block.pointer;
350        let mut cur_size = original_block.size;
351        while cur_size > 0 {
352            // Split.
353            records.add_record(AccessAdapterRecord {
354                timestamp,
355                address_space: F::from_canonical_u32(address_space),
356                start_index: F::from_canonical_u32(cur_ptr),
357                data: data[(cur_ptr - original_block.pointer) as usize
358                    ..(cur_ptr - original_block.pointer) as usize + cur_size]
359                    .to_vec(),
360                kind: AccessAdapterRecordKind::Split,
361            });
362
363            let half_size = cur_size / 2;
364            let half_size_u32 = half_size as u32;
365            let mid_ptr = cur_ptr + half_size_u32;
366
367            if query <= mid_ptr {
368                // The right is finalized; add it to the partition.
369                let block = BlockData {
370                    pointer: mid_ptr,
371                    size: half_size,
372                    timestamp,
373                };
374                self.block_data
375                    .set_range(&(address_space, mid_ptr), half_size, block);
376            }
377            if query >= cur_ptr + half_size_u32 {
378                // The left is finalized; add it to the partition.
379                let block = BlockData {
380                    pointer: cur_ptr,
381                    size: half_size,
382                    timestamp,
383                };
384                self.block_data
385                    .set_range(&(address_space, cur_ptr), half_size, block);
386            }
387            if mid_ptr <= query {
388                cur_ptr = mid_ptr;
389            }
390            if cur_ptr == query {
391                break;
392            }
393            cur_size = half_size;
394        }
395    }
396
397    fn access_updating_timestamp(
398        &mut self,
399        address_space: u32,
400        pointer: u32,
401        size: usize,
402        records: &mut AccessAdapterInventory<F>,
403    ) -> u32 {
404        self.access(address_space, pointer, size, records);
405
406        let mut prev_timestamp = None;
407
408        let mut i = 0;
409        while i < size as u32 {
410            let block = self.block_data.get_mut(&(address_space, pointer + i));
411            debug_assert!(i == 0 || prev_timestamp == Some(block.timestamp));
412            prev_timestamp = Some(block.timestamp);
413            block.timestamp = self.timestamp;
414            i = block.pointer + block.size as u32;
415        }
416        prev_timestamp.unwrap()
417    }
418
419    fn access(
420        &mut self,
421        address_space: u32,
422        pointer: u32,
423        size: usize,
424        records: &mut AccessAdapterInventory<F>,
425    ) {
426        self.split_to_make_boundary(address_space, pointer, records);
427        self.split_to_make_boundary(address_space, pointer + size as u32, records);
428
429        let block_data = self.block_containing(address_space, pointer);
430
431        if block_data.pointer == pointer && block_data.size == size {
432            return;
433        }
434        assert!(size > 1);
435
436        // Now recursively access left and right blocks to ensure they are in the partition.
437        let half_size = size / 2;
438        self.access(address_space, pointer, half_size, records);
439        self.access(
440            address_space,
441            pointer + half_size as u32,
442            half_size,
443            records,
444        );
445
446        self.merge_block_with_next(address_space, pointer, records);
447    }
448
449    /// Merges the two adjacent blocks starting at (address_space, pointer).
450    ///
451    /// Panics if there is no block starting at (address_space, pointer) or if the two blocks
452    /// do not have the same size.
453    fn merge_block_with_next(
454        &mut self,
455        address_space: u32,
456        pointer: u32,
457        records: &mut AccessAdapterInventory<F>,
458    ) {
459        let left_block = self.block_data.get(&(address_space, pointer));
460
461        let left_timestamp = left_block.timestamp;
462        let size = left_block.size;
463
464        let right_timestamp = self
465            .block_data
466            .get(&(address_space, pointer + size as u32))
467            .timestamp;
468
469        let timestamp = max(left_timestamp, right_timestamp);
470        self.block_data.set_range(
471            &(address_space, pointer),
472            2 * size,
473            BlockData {
474                pointer,
475                size: 2 * size,
476                timestamp,
477            },
478        );
479        records.add_record(AccessAdapterRecord {
480            timestamp,
481            address_space: F::from_canonical_u32(address_space),
482            start_index: F::from_canonical_u32(pointer),
483            data: self.range_vec(address_space, pointer, 2 * size),
484            kind: AccessAdapterRecordKind::Merge {
485                left_timestamp,
486                right_timestamp,
487            },
488        });
489    }
490
491    fn block_containing(&mut self, address_space: u32, pointer: u32) -> BlockData {
492        self.block_data
493            .get_without_adding(&(address_space, pointer))
494    }
495
496    pub fn get(&self, address_space: u32, pointer: u32) -> F {
497        self.data[(address_space - self.as_offset) as usize]
498            .get(pointer as usize)
499            .cloned()
500            .unwrap_or_default()
501    }
502
503    fn range_array<const N: usize>(&self, address_space: u32, pointer: u32) -> [F; N] {
504        array::from_fn(|i| self.get(address_space, pointer + i as u32))
505    }
506
507    fn range_vec(&self, address_space: u32, pointer: u32, len: usize) -> Vec<F> {
508        let pointer = pointer as usize;
509        self.data[(address_space - self.as_offset) as usize].range_vec(pointer..pointer + len)
510    }
511
512    pub fn aux_cols_factory(&self) -> MemoryAuxColsFactory<F> {
513        let range_bus = self.range_checker.bus();
514        MemoryAuxColsFactory {
515            range_checker: self.range_checker.clone(),
516            timestamp_lt_air: AssertLtSubAir::new(range_bus, self.timestamp_max_bits),
517            _marker: Default::default(),
518        }
519    }
520
521    // just for unit testing
522    #[cfg(test)]
523    fn last_record(&self) -> &MemoryRecord<F> {
524        self.log.last().unwrap().as_ref().unwrap()
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use openvm_circuit_primitives::var_range::{
531        SharedVariableRangeCheckerChip, VariableRangeCheckerBus,
532    };
533    use openvm_stark_backend::p3_field::FieldAlgebra;
534    use openvm_stark_sdk::p3_baby_bear::BabyBear;
535
536    use super::{BlockData, MemoryRecord, OfflineMemory};
537    use crate::{
538        arch::MemoryConfig,
539        system::memory::{
540            adapter::{AccessAdapterInventory, AccessAdapterRecord, AccessAdapterRecordKind},
541            offline_checker::MemoryBus,
542            paged_vec::AddressMap,
543            MemoryImage, TimestampedValues,
544        },
545    };
546
547    macro_rules! bb {
548        ($x:expr) => {
549            BabyBear::from_canonical_u32($x)
550        };
551    }
552
553    macro_rules! bba {
554        [$($x:expr),*] => {
555            [$(BabyBear::from_canonical_u32($x)),*]
556        }
557    }
558
559    macro_rules! bbvec {
560        [$($x:expr),*] => {
561            vec![$(BabyBear::from_canonical_u32($x)),*]
562        }
563    }
564
565    fn setup_test(
566        initial_memory: MemoryImage<BabyBear>,
567        initial_block_size: usize,
568    ) -> (OfflineMemory<BabyBear>, AccessAdapterInventory<BabyBear>) {
569        let memory_bus = MemoryBus::new(0);
570        let range_checker =
571            SharedVariableRangeCheckerChip::new(VariableRangeCheckerBus::new(1, 29));
572        let mem_config = MemoryConfig {
573            as_offset: initial_memory.as_offset,
574            ..Default::default()
575        };
576        let memory = OfflineMemory::new(
577            initial_memory,
578            initial_block_size,
579            memory_bus,
580            range_checker.clone(),
581            mem_config,
582        );
583        let access_adapter_inventory = AccessAdapterInventory::new(
584            range_checker,
585            memory_bus,
586            mem_config.clk_max_bits,
587            mem_config.max_access_adapter_n,
588        );
589        (memory, access_adapter_inventory)
590    }
591
592    #[test]
593    fn test_partition() {
594        let initial_memory = AddressMap::new(0, 1, 16);
595        let (mut memory, _) = setup_test(initial_memory, 8);
596        assert_eq!(
597            memory.block_containing(1, 13),
598            BlockData {
599                pointer: 8,
600                size: 8,
601                timestamp: 0,
602            }
603        );
604
605        assert_eq!(
606            memory.block_containing(1, 8),
607            BlockData {
608                pointer: 8,
609                size: 8,
610                timestamp: 0,
611            }
612        );
613
614        assert_eq!(
615            memory.block_containing(1, 15),
616            BlockData {
617                pointer: 8,
618                size: 8,
619                timestamp: 0,
620            }
621        );
622
623        assert_eq!(
624            memory.block_containing(1, 16),
625            BlockData {
626                pointer: 16,
627                size: 8,
628                timestamp: 0,
629            }
630        );
631    }
632
633    #[test]
634    fn test_write_read_initial_block_len_1() {
635        let (mut memory, mut access_adapters) = setup_test(MemoryImage::default(), 1);
636        let address_space = 1;
637
638        memory.write(address_space, 0, bbvec![1, 2, 3, 4], &mut access_adapters);
639
640        memory.read(address_space, 0, 2, &mut access_adapters);
641        let read_record = memory.last_record();
642        assert_eq!(read_record.data, bba![1, 2]);
643
644        memory.write(address_space, 2, bbvec![100], &mut access_adapters);
645
646        memory.read(address_space, 0, 4, &mut access_adapters);
647        let read_record = memory.last_record();
648        assert_eq!(read_record.data, bba![1, 2, 100, 4]);
649    }
650
651    #[test]
652    fn test_records_initial_block_len_1() {
653        let (mut memory, mut adapter_records) = setup_test(MemoryImage::default(), 1);
654
655        memory.write(1, 0, bbvec![1, 2, 3, 4], &mut adapter_records);
656
657        // Above write first causes merge of [0:1] and [1:2] into [0:2].
658        assert_eq!(
659            adapter_records.records_for_n(2)[0],
660            AccessAdapterRecord {
661                timestamp: 0,
662                address_space: bb!(1),
663                start_index: bb!(0),
664                data: bbvec![0, 0],
665                kind: AccessAdapterRecordKind::Merge {
666                    left_timestamp: 0,
667                    right_timestamp: 0,
668                },
669            }
670        );
671        // then merge [2:3] and [3:4] into [2:4].
672        assert_eq!(
673            adapter_records.records_for_n(2)[1],
674            AccessAdapterRecord {
675                timestamp: 0,
676                address_space: bb!(1),
677                start_index: bb!(2),
678                data: bbvec![0, 0],
679                kind: AccessAdapterRecordKind::Merge {
680                    left_timestamp: 0,
681                    right_timestamp: 0,
682                },
683            }
684        );
685        // then merge [0:2] and [2:4] into [0:4].
686        assert_eq!(
687            adapter_records.records_for_n(4)[0],
688            AccessAdapterRecord {
689                timestamp: 0,
690                address_space: bb!(1),
691                start_index: bb!(0),
692                data: bbvec![0, 0, 0, 0],
693                kind: AccessAdapterRecordKind::Merge {
694                    left_timestamp: 0,
695                    right_timestamp: 0,
696                },
697            }
698        );
699        // At time 1 we write [0:4].
700        let write_record = memory.last_record();
701        assert_eq!(
702            write_record,
703            &MemoryRecord {
704                address_space: bb!(1),
705                pointer: bb!(0),
706                timestamp: 1,
707                prev_timestamp: 0,
708                data: bbvec![1, 2, 3, 4],
709                prev_data: Some(bbvec![0, 0, 0, 0]),
710            }
711        );
712        assert_eq!(memory.timestamp(), 2);
713        assert_eq!(adapter_records.total_records(), 3);
714
715        memory.read(1, 0, 4, &mut adapter_records);
716        let read_record = memory.last_record();
717        // At time 2 we read [0:4].
718        assert_eq!(adapter_records.total_records(), 3);
719        assert_eq!(
720            read_record,
721            &MemoryRecord {
722                address_space: bb!(1),
723                pointer: bb!(0),
724                timestamp: 2,
725                prev_timestamp: 1,
726                data: bbvec![1, 2, 3, 4],
727                prev_data: None,
728            }
729        );
730        assert_eq!(memory.timestamp(), 3);
731
732        memory.write(1, 0, bbvec![10, 11], &mut adapter_records);
733        let write_record = memory.last_record();
734        // write causes split [0:4] into [0:2] and [2:4] (to prepare for write to [0:2]).
735        assert_eq!(adapter_records.total_records(), 4);
736        assert_eq!(
737            adapter_records.records_for_n(4).last().unwrap(),
738            &AccessAdapterRecord {
739                timestamp: 2,
740                address_space: bb!(1),
741                start_index: bb!(0),
742                data: bbvec![1, 2, 3, 4],
743                kind: AccessAdapterRecordKind::Split,
744            }
745        );
746
747        // At time 3 we write [10, 11] into [0, 2].
748        assert_eq!(
749            write_record,
750            &MemoryRecord {
751                address_space: bb!(1),
752                pointer: bb!(0),
753                timestamp: 3,
754                prev_timestamp: 2,
755                data: bbvec![10, 11],
756                prev_data: Some(bbvec![1, 2]),
757            }
758        );
759
760        memory.read(1, 0, 4, &mut adapter_records);
761        let read_record = memory.last_record();
762        assert_eq!(adapter_records.total_records(), 5);
763        assert_eq!(
764            adapter_records.records_for_n(4).last().unwrap(),
765            &AccessAdapterRecord {
766                timestamp: 3,
767                address_space: bb!(1),
768                start_index: bb!(0),
769                data: bbvec![10, 11, 3, 4],
770                kind: AccessAdapterRecordKind::Merge {
771                    left_timestamp: 3,
772                    right_timestamp: 2
773                },
774            }
775        );
776        // At time 9 we read [0:4].
777        assert_eq!(
778            read_record,
779            &MemoryRecord {
780                address_space: bb!(1),
781                pointer: bb!(0),
782                timestamp: 4,
783                prev_timestamp: 3,
784                data: bbvec![10, 11, 3, 4],
785                prev_data: None,
786            }
787        );
788    }
789
790    #[test]
791    fn test_records_initial_block_len_8() {
792        let (mut memory, mut adapter_records) = setup_test(MemoryImage::default(), 8);
793
794        memory.write(1, 0, bbvec![1, 2, 3, 4], &mut adapter_records);
795        let write_record = memory.last_record();
796
797        // Above write first causes split of [0:8] into [0:4] and [4:8].
798        assert_eq!(adapter_records.total_records(), 1);
799        assert_eq!(
800            adapter_records.records_for_n(8).last().unwrap(),
801            &AccessAdapterRecord {
802                timestamp: 0,
803                address_space: bb!(1),
804                start_index: bb!(0),
805                data: bbvec![0, 0, 0, 0, 0, 0, 0, 0],
806                kind: AccessAdapterRecordKind::Split,
807            }
808        );
809        // At time 1 we write [0:4].
810        assert_eq!(
811            write_record,
812            &MemoryRecord {
813                address_space: bb!(1),
814                pointer: bb!(0),
815                timestamp: 1,
816                prev_timestamp: 0,
817                data: bbvec![1, 2, 3, 4],
818                prev_data: Some(bbvec![0, 0, 0, 0]),
819            }
820        );
821        assert_eq!(memory.timestamp(), 2);
822
823        memory.read(1, 0, 4, &mut adapter_records);
824        let read_record = memory.last_record();
825        // At time 2 we read [0:4].
826        assert_eq!(adapter_records.total_records(), 1);
827        assert_eq!(
828            read_record,
829            &MemoryRecord {
830                address_space: bb!(1),
831                pointer: bb!(0),
832                timestamp: 2,
833                prev_timestamp: 1,
834                data: bbvec![1, 2, 3, 4],
835                prev_data: None,
836            }
837        );
838        assert_eq!(memory.timestamp(), 3);
839
840        memory.write(1, 0, bbvec![10, 11], &mut adapter_records);
841        let write_record = memory.last_record();
842        // write causes split [0:4] into [0:2] and [2:4] (to prepare for write to [0:2]).
843        assert_eq!(adapter_records.total_records(), 2);
844        assert_eq!(
845            adapter_records.records_for_n(4).last().unwrap(),
846            &AccessAdapterRecord {
847                timestamp: 2,
848                address_space: bb!(1),
849                start_index: bb!(0),
850                data: bbvec![1, 2, 3, 4],
851                kind: AccessAdapterRecordKind::Split,
852            }
853        );
854
855        // At time 3 we write [10, 11] into [0, 2].
856        assert_eq!(
857            write_record,
858            &MemoryRecord {
859                address_space: bb!(1),
860                pointer: bb!(0),
861                timestamp: 3,
862                prev_timestamp: 2,
863                data: bbvec![10, 11],
864                prev_data: Some(bbvec![1, 2]),
865            }
866        );
867
868        memory.read(1, 0, 4, &mut adapter_records);
869        let read_record = memory.last_record();
870        assert_eq!(adapter_records.total_records(), 3);
871        assert_eq!(
872            adapter_records.records_for_n(4).last().unwrap(),
873            &AccessAdapterRecord {
874                timestamp: 3,
875                address_space: bb!(1),
876                start_index: bb!(0),
877                data: bbvec![10, 11, 3, 4],
878                kind: AccessAdapterRecordKind::Merge {
879                    left_timestamp: 3,
880                    right_timestamp: 2
881                },
882            }
883        );
884        // At time 9 we read [0:4].
885        assert_eq!(
886            read_record,
887            &MemoryRecord {
888                address_space: bb!(1),
889                pointer: bb!(0),
890                timestamp: 4,
891                prev_timestamp: 3,
892                data: bbvec![10, 11, 3, 4],
893                prev_data: None,
894            }
895        );
896    }
897
898    #[test]
899    fn test_get_initial_block_len_1() {
900        let (mut memory, mut adapter_records) = setup_test(MemoryImage::default(), 1);
901
902        memory.write(2, 0, bbvec![4, 3, 2, 1], &mut adapter_records);
903
904        assert_eq!(memory.get(2, 0), BabyBear::from_canonical_u32(4));
905        assert_eq!(memory.get(2, 1), BabyBear::from_canonical_u32(3));
906        assert_eq!(memory.get(2, 2), BabyBear::from_canonical_u32(2));
907        assert_eq!(memory.get(2, 3), BabyBear::from_canonical_u32(1));
908        assert_eq!(memory.get(2, 5), BabyBear::ZERO);
909
910        assert_eq!(memory.get(1, 0), BabyBear::ZERO);
911    }
912
913    #[test]
914    fn test_get_initial_block_len_8() {
915        let (mut memory, mut adapter_records) = setup_test(MemoryImage::default(), 8);
916
917        memory.write(2, 0, bbvec![4, 3, 2, 1], &mut adapter_records);
918
919        assert_eq!(memory.get(2, 0), BabyBear::from_canonical_u32(4));
920        assert_eq!(memory.get(2, 1), BabyBear::from_canonical_u32(3));
921        assert_eq!(memory.get(2, 2), BabyBear::from_canonical_u32(2));
922        assert_eq!(memory.get(2, 3), BabyBear::from_canonical_u32(1));
923        assert_eq!(memory.get(2, 5), BabyBear::ZERO);
924        assert_eq!(memory.get(2, 9), BabyBear::ZERO);
925        assert_eq!(memory.get(1, 0), BabyBear::ZERO);
926    }
927
928    #[test]
929    fn test_finalize_empty() {
930        let (mut memory, mut adapter_records) = setup_test(MemoryImage::default(), 4);
931
932        let memory = memory.finalize::<4>(&mut adapter_records);
933        assert_eq!(memory.len(), 0);
934        assert_eq!(adapter_records.total_records(), 0);
935    }
936
937    #[test]
938    fn test_finalize_block_len_8() {
939        let (mut memory, mut adapter_records) = setup_test(MemoryImage::default(), 8);
940        // Make block 0:4 in address space 1 active.
941        memory.write(1, 0, bbvec![1, 2, 3, 4], &mut adapter_records);
942
943        // Make block 16:32 in address space 1 active.
944        memory.write(
945            1,
946            16,
947            bbvec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
948            &mut adapter_records,
949        );
950
951        // Make block 64:72 in address space 2 active.
952        memory.write(2, 64, bbvec![8, 7, 6, 5, 4, 3, 2, 1], &mut adapter_records);
953
954        let num_records_before_finalize = adapter_records.total_records();
955
956        // Finalize to a partition of size 8.
957        let final_memory = memory.finalize::<8>(&mut adapter_records);
958        assert_eq!(final_memory.len(), 4);
959        assert_eq!(
960            final_memory.get(&(1, 0)),
961            Some(&TimestampedValues {
962                values: bba![1, 2, 3, 4, 0, 0, 0, 0],
963                timestamp: 1,
964            })
965        );
966        // start_index = 16 corresponds to label = 2
967        assert_eq!(
968            final_memory.get(&(1, 2)),
969            Some(&TimestampedValues {
970                values: bba![1, 1, 1, 1, 1, 1, 1, 1],
971                timestamp: 2,
972            })
973        );
974        // start_index = 24 corresponds to label = 3
975        assert_eq!(
976            final_memory.get(&(1, 3)),
977            Some(&TimestampedValues {
978                values: bba![1, 1, 1, 1, 1, 1, 1, 1],
979                timestamp: 2,
980            })
981        );
982        // start_index = 64 corresponds to label = 8
983        assert_eq!(
984            final_memory.get(&(2, 8)),
985            Some(&TimestampedValues {
986                values: bba![8, 7, 6, 5, 4, 3, 2, 1],
987                timestamp: 3,
988            })
989        );
990
991        // We need to do 1 + 1 + 0 = 2 adapters.
992        assert_eq!(
993            adapter_records.total_records() - num_records_before_finalize,
994            2
995        );
996    }
997
998    #[test]
999    fn test_write_read_initial_block_len_8_initial_memory() {
1000        type F = BabyBear;
1001
1002        // Initialize initial memory with blocks at indices 0 and 2
1003        let mut initial_memory = MemoryImage::default();
1004        for i in 0..8 {
1005            initial_memory.insert(&(1, i), F::from_canonical_u32(i + 1));
1006            initial_memory.insert(&(1, 16 + i), F::from_canonical_u32(i + 1));
1007        }
1008
1009        let (mut memory, mut adapter_records) = setup_test(initial_memory, 8);
1010
1011        // Verify initial state of block 0 (pointers 0–8)
1012        memory.read(1, 0, 8, &mut adapter_records);
1013        let initial_read_record_0 = memory.last_record();
1014        assert_eq!(initial_read_record_0.data, bbvec![1, 2, 3, 4, 5, 6, 7, 8]);
1015
1016        // Verify initial state of block 2 (pointers 16–24)
1017        memory.read(1, 16, 8, &mut adapter_records);
1018        let initial_read_record_2 = memory.last_record();
1019        assert_eq!(initial_read_record_2.data, bbvec![1, 2, 3, 4, 5, 6, 7, 8]);
1020
1021        // Test: Write a partial block to block 0 (pointer 0) and read back partially and fully
1022        memory.write(1, 0, bbvec![9, 9, 9, 9], &mut adapter_records);
1023        memory.read(1, 0, 2, &mut adapter_records);
1024        let partial_read_record = memory.last_record();
1025        assert_eq!(partial_read_record.data, bbvec![9, 9]);
1026
1027        memory.read(1, 0, 8, &mut adapter_records);
1028        let full_read_record_0 = memory.last_record();
1029        assert_eq!(full_read_record_0.data, bbvec![9, 9, 9, 9, 5, 6, 7, 8]);
1030
1031        // Test: Write a single element to pointer 2 and verify read in different lengths
1032        memory.write(1, 2, bbvec![100], &mut adapter_records);
1033        memory.read(1, 1, 4, &mut adapter_records);
1034        let read_record_4 = memory.last_record();
1035        assert_eq!(read_record_4.data, bbvec![9, 100, 9, 5]);
1036
1037        memory.read(1, 2, 8, &mut adapter_records);
1038        let full_read_record_2 = memory.last_record();
1039        assert_eq!(full_read_record_2.data, bba![100, 9, 5, 6, 7, 8, 0, 0]);
1040
1041        // Test: Write and read at the last pointer in block 2 (pointer 23, part of key (1, 2))
1042        memory.write(1, 23, bbvec![77], &mut adapter_records);
1043        memory.read(1, 23, 2, &mut adapter_records);
1044        let boundary_read_record = memory.last_record();
1045        assert_eq!(boundary_read_record.data, bba![77, 0]); // Last byte modified, ensuring boundary check
1046
1047        // Test: Reading from an uninitialized block (should default to 0)
1048        memory.read(1, 10, 4, &mut adapter_records);
1049        let default_read_record = memory.last_record();
1050        assert_eq!(default_read_record.data, bba![0, 0, 0, 0]);
1051
1052        memory.read(1, 100, 4, &mut adapter_records);
1053        let default_read_record = memory.last_record();
1054        assert_eq!(default_read_record.data, bba![0, 0, 0, 0]);
1055
1056        // Test: Overwrite entire memory pointer 16–24 and verify
1057        memory.write(
1058            1,
1059            16,
1060            bbvec![50, 50, 50, 50, 50, 50, 50, 50],
1061            &mut adapter_records,
1062        );
1063        memory.read(1, 16, 8, &mut adapter_records);
1064        let overwrite_read_record = memory.last_record();
1065        assert_eq!(
1066            overwrite_read_record.data,
1067            bba![50, 50, 50, 50, 50, 50, 50, 50]
1068        ); // Verify entire block overwrite
1069    }
1070}