1use std::{array::from_fn, fmt::Debug, num::NonZero};
2
3use getset::Getters;
4use itertools::zip_eq;
5use openvm_instructions::exe::SparseMemoryImage;
6use openvm_stark_backend::{
7 p3_field::{Field, PrimeField32},
8 p3_maybe_rayon::prelude::*,
9 p3_util::log2_strict_usize,
10};
11use tracing::instrument;
12
13use crate::{
14 arch::{
15 AddressSpaceHostConfig, AddressSpaceHostLayout, DenseRecordArena, MemoryConfig,
16 RecordArena, MAX_CELL_BYTE_SIZE,
17 },
18 system::{
19 memory::{
20 adapter::records::{AccessLayout, AccessRecordHeader, MERGE_AND_NOT_SPLIT_FLAG},
21 MemoryAddress, TimestampedEquipartition, TimestampedValues, CHUNK,
22 },
23 TouchedMemory,
24 },
25 utils::slice_as_bytes,
26};
27
28mod basic;
29#[cfg(any(unix, windows))]
30mod memmap;
31mod paged_vec;
32
33#[cfg(not(any(unix, windows)))]
34pub use basic::*;
35#[cfg(any(unix, windows))]
36pub use memmap::*;
37pub use paged_vec::PagedVec;
38
39#[cfg(all(any(unix, windows), not(feature = "basic-memory")))]
40pub type MemoryBackend = memmap::MmapMemory;
41#[cfg(any(not(any(unix, windows)), feature = "basic-memory"))]
42pub type MemoryBackend = basic::BasicMemory;
43
44pub const INITIAL_TIMESTAMP: u32 = 0;
45pub const PAGE_SIZE: usize = 4096;
47
48const MAX_BLOCK_SIZE: usize = 32;
50const MIN_ALIGN: usize = 1;
51const MAX_SEGMENTS: usize = MAX_BLOCK_SIZE / MIN_ALIGN;
52
53pub type Address = (u32, u32);
55
56pub trait LinearMemory {
58 fn new(size: usize) -> Self;
60 fn size(&self) -> usize;
62 fn as_slice(&self) -> &[u8];
64 fn as_mut_slice(&mut self) -> &mut [u8];
66 fn fill_zero(&mut self) {
68 self.as_mut_slice().fill(0);
69 }
70 unsafe fn read<BLOCK: Copy>(&self, from: usize) -> BLOCK;
81 unsafe fn read_unaligned<BLOCK: Copy>(&self, from: usize) -> BLOCK;
91 unsafe fn write<BLOCK: Copy>(&mut self, start: usize, values: BLOCK);
101 unsafe fn write_unaligned<BLOCK: Copy>(&mut self, start: usize, values: BLOCK);
110 unsafe fn swap<BLOCK: Copy>(&mut self, start: usize, values: &mut BLOCK);
120 unsafe fn copy_nonoverlapping<T: Copy>(&mut self, to: usize, data: &[T]);
131 unsafe fn get_aligned_slice<T: Copy>(&self, start: usize, len: usize) -> &[T];
140}
141
142#[derive(Debug, Clone)]
148pub struct AddressMap<M: LinearMemory = MemoryBackend> {
149 pub mem: Vec<M>,
151 pub config: Vec<AddressSpaceHostConfig>,
153}
154
155impl Default for AddressMap {
156 fn default() -> Self {
157 Self::from_mem_config(&MemoryConfig::default())
158 }
159}
160
161impl<M: LinearMemory> AddressMap<M> {
162 pub fn new(config: Vec<AddressSpaceHostConfig>) -> Self {
163 assert_eq!(config[0].num_cells, 0, "Address space 0 must have 0 cells");
164 let mem = config
165 .iter()
166 .map(|config| M::new(config.num_cells.checked_mul(config.layout.size()).unwrap()))
167 .collect();
168 Self { mem, config }
169 }
170
171 pub fn from_mem_config(mem_config: &MemoryConfig) -> Self {
172 Self::new(mem_config.addr_spaces.clone())
173 }
174
175 #[inline(always)]
176 pub fn get_memory(&self) -> &Vec<M> {
177 &self.mem
178 }
179
180 #[inline(always)]
181 pub fn get_memory_mut(&mut self) -> &mut Vec<M> {
182 &mut self.mem
183 }
184
185 pub fn fill_zero(&mut self) {
187 for mem in &mut self.mem {
188 mem.fill_zero();
189 }
190 }
191
192 pub unsafe fn get_f<F: PrimeField32>(&self, addr_space: u32, ptr: u32) -> F {
195 let layout = &self.config.get_unchecked(addr_space as usize).layout;
196 let start = ptr as usize * layout.size();
197 let bytes = self.get_u8_slice(addr_space, start, layout.size());
198 layout.to_field(bytes)
199 }
200
201 pub unsafe fn get<T: Copy>(&self, (addr_space, ptr): Address) -> T {
205 debug_assert_eq!(
206 size_of::<T>(),
207 self.config[addr_space as usize].layout.size()
208 );
209 self.mem
212 .get_unchecked(addr_space as usize)
213 .read((ptr as usize) * size_of::<T>())
214 }
215
216 pub unsafe fn get_slice<T: Copy + Debug>(
222 &self,
223 (addr_space, ptr): Address,
224 len: usize,
225 ) -> &[T] {
226 debug_assert_eq!(
227 size_of::<T>(),
228 self.config[addr_space as usize].layout.size()
229 );
230 let start = (ptr as usize) * size_of::<T>();
231 let mem = self.mem.get_unchecked(addr_space as usize);
232 mem.get_aligned_slice(start, len)
235 }
236
237 pub unsafe fn get_u8_slice(&self, addr_space: u32, start: usize, len: usize) -> &[u8] {
243 let mem = self.mem.get_unchecked(addr_space as usize);
244 mem.get_aligned_slice(start, len)
245 }
246
247 pub unsafe fn copy_slice_nonoverlapping<T: Copy>(
255 &mut self,
256 (addr_space, ptr): Address,
257 data: &[T],
258 ) {
259 let start = (ptr as usize) * size_of::<T>();
260 self.mem
265 .get_unchecked_mut(addr_space as usize)
266 .copy_nonoverlapping(start, data);
267 }
268
269 pub fn set_from_sparse(&mut self, sparse_map: &SparseMemoryImage) {
274 for (&(addr_space, index), &data_byte) in sparse_map.iter() {
275 unsafe {
278 self.mem
279 .get_unchecked_mut(addr_space as usize)
280 .write_unaligned(index as usize, data_byte);
281 }
282 }
283 }
284}
285
286#[derive(Debug, Clone)]
290pub struct GuestMemory {
291 pub memory: AddressMap,
292}
293
294impl GuestMemory {
295 pub fn new(addr: AddressMap) -> Self {
296 Self { memory: addr }
297 }
298
299 #[inline(always)]
307 pub unsafe fn read<T, const BLOCK_SIZE: usize>(
308 &self,
309 addr_space: u32,
310 ptr: u32,
311 ) -> [T; BLOCK_SIZE]
312 where
313 T: Copy + Debug,
314 {
315 self.debug_assert_cell_type::<T>(addr_space);
316 self.memory
320 .get_memory()
321 .get_unchecked(addr_space as usize)
322 .read((ptr as usize) * size_of::<T>())
323 }
324
325 #[inline(always)]
330 pub unsafe fn write<T, const BLOCK_SIZE: usize>(
331 &mut self,
332 addr_space: u32,
333 ptr: u32,
334 values: [T; BLOCK_SIZE],
335 ) where
336 T: Copy + Debug,
337 {
338 self.debug_assert_cell_type::<T>(addr_space);
339 self.memory
342 .get_memory_mut()
343 .get_unchecked_mut(addr_space as usize)
344 .write((ptr as usize) * size_of::<T>(), values);
345 }
346
347 #[inline(always)]
352 pub unsafe fn swap<T, const BLOCK_SIZE: usize>(
353 &mut self,
354 addr_space: u32,
355 ptr: u32,
356 values: &mut [T; BLOCK_SIZE],
357 ) where
358 T: Copy + Debug,
359 {
360 self.debug_assert_cell_type::<T>(addr_space);
361 self.memory
364 .get_memory_mut()
365 .get_unchecked_mut(addr_space as usize)
366 .swap((ptr as usize) * size_of::<T>(), values);
367 }
368
369 #[inline(always)]
370 #[allow(clippy::missing_safety_doc)]
371 pub unsafe fn get_slice<T: Copy + Debug>(&self, addr_space: u32, ptr: u32, len: usize) -> &[T] {
372 self.memory.get_slice((addr_space, ptr), len)
373 }
374
375 #[inline(always)]
376 fn debug_assert_cell_type<T>(&self, addr_space: u32) {
377 debug_assert_eq!(
378 size_of::<T>(),
379 self.memory.config[addr_space as usize].layout.size()
380 );
381 }
382}
383
384#[repr(C)]
385#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
386pub struct AccessMetadata {
387 pub timestamp_and_log_block_size: u32,
389 pub offset_to_start: u8,
391}
392
393impl AccessMetadata {
394 const TIMESTAMP_MASK: u32 = (1 << 29) - 1;
395 const LOG_BLOCK_SIZE_SHIFT: u32 = 29;
396
397 pub fn new(timestamp: u32, block_size: u8, offset_to_start: u8) -> Self {
398 debug_assert!(timestamp < (1 << 29), "Timestamp must be less than 2^29");
399 debug_assert!(
400 block_size == 0 || (block_size.is_power_of_two() && block_size <= MAX_BLOCK_SIZE as u8),
401 "Block size must be 0 or power of 2 and <= {}",
402 MAX_BLOCK_SIZE
403 );
404
405 let encoded_block_size = if block_size == 0 {
406 0
407 } else {
408 unsafe { NonZero::new_unchecked(block_size) }.ilog2() + 1
410 };
411 let packed = timestamp | (encoded_block_size << Self::LOG_BLOCK_SIZE_SHIFT);
412
413 Self {
414 timestamp_and_log_block_size: packed,
415 offset_to_start,
416 }
417 }
418
419 pub fn timestamp(&self) -> u32 {
420 self.timestamp_and_log_block_size & Self::TIMESTAMP_MASK
421 }
422
423 pub fn block_size(&self) -> u8 {
424 let encoded = self.timestamp_and_log_block_size >> Self::LOG_BLOCK_SIZE_SHIFT;
425 if encoded == 0 {
426 0
427 } else {
428 1 << (encoded - 1)
429 }
430 }
431}
432
433#[derive(Getters)]
436pub struct TracingMemory {
437 pub timestamp: u32,
438 initial_block_size: usize,
440 #[getset(get = "pub")]
442 pub data: GuestMemory,
443 pub(super) meta: Vec<PagedVec<AccessMetadata, PAGE_SIZE>>,
448 pub min_block_size: Vec<u32>,
451 pub access_adapter_records: DenseRecordArena,
452}
453
454const INITIAL_CELL_BUFFER: &[u8] = &[0u8; 8];
456const INITIAL_TIMESTAMP_BUFFER: &[u32] = &[INITIAL_TIMESTAMP; 8];
458
459impl TracingMemory {
460 pub fn new(
461 mem_config: &MemoryConfig,
462 initial_block_size: usize,
463 access_adapter_arena_size_bound: usize,
464 ) -> Self {
465 let image = GuestMemory::new(AddressMap::from_mem_config(mem_config));
466 Self::from_image(image, initial_block_size, access_adapter_arena_size_bound)
467 }
468
469 pub fn from_image(
471 image: GuestMemory,
472 initial_block_size: usize,
473 access_adapter_arena_size_bound: usize,
474 ) -> Self {
475 let (meta, min_block_size): (Vec<_>, Vec<_>) =
476 zip_eq(image.memory.get_memory(), &image.memory.config)
477 .map(|(mem, addr_sp)| {
478 let num_cells = mem.size() / addr_sp.layout.size();
479 let min_block_size = addr_sp.min_block_size;
480 let total_metadata_len = num_cells.div_ceil(min_block_size);
481 (PagedVec::new(total_metadata_len), min_block_size as u32)
482 })
483 .unzip();
484 let access_adapter_records =
485 DenseRecordArena::with_byte_capacity(access_adapter_arena_size_bound);
486 Self {
487 data: image,
488 meta,
489 min_block_size,
490 timestamp: INITIAL_TIMESTAMP + 1,
491 initial_block_size,
492 access_adapter_records,
493 }
494 }
495
496 #[inline(always)]
497 fn assert_alignment(&self, block_size: usize, align: usize, addr_space: u32, ptr: u32) {
498 debug_assert!(block_size.is_power_of_two());
499 debug_assert_eq!(block_size % align, 0);
500 debug_assert_ne!(addr_space, 0);
501 debug_assert_eq!(align as u32, self.min_block_size[addr_space as usize]);
502 assert_eq!(
503 ptr % (align as u32),
504 0,
505 "pointer={ptr} not aligned to {align}"
506 );
507 }
508
509 #[inline(always)]
512 fn get_block_metadata<const ALIGN: usize>(
513 &mut self,
514 address_space: usize,
515 pointer: usize,
516 ) -> (u32, AccessMetadata) {
517 let ptr_index = pointer / ALIGN;
518 let meta_page = unsafe { self.meta.get_unchecked_mut(address_space) };
522 let current_meta = meta_page.get(ptr_index);
523
524 let (block_start_index, block_metadata) = if current_meta.offset_to_start == 0 {
525 (ptr_index, current_meta)
526 } else {
527 let offset = current_meta.offset_to_start;
528 let start_idx = ptr_index - offset as usize;
529 let start_meta = meta_page.get(start_idx);
530 (start_idx, start_meta)
531 };
532
533 let block_start_pointer = (block_start_index * ALIGN) as u32;
534
535 (block_start_pointer, block_metadata)
536 }
537
538 #[inline(always)]
539 fn get_timestamp<const ALIGN: usize>(&mut self, address_space: usize, pointer: usize) -> u32 {
540 let ptr_index = pointer / ALIGN;
541 let meta_page = unsafe { self.meta.get_unchecked_mut(address_space) };
545 let current_meta = meta_page.get(ptr_index);
546
547 if current_meta.offset_to_start == 0 {
548 current_meta.timestamp()
549 } else {
550 let offset = current_meta.offset_to_start;
551 let block_start_index = ptr_index - offset as usize;
552 meta_page.get(block_start_index).timestamp()
553 }
554 }
555
556 #[inline(always)]
559 fn set_meta_block<const BLOCK_SIZE: usize, const ALIGN: usize>(
560 &mut self,
561 address_space: usize,
562 pointer: usize,
563 timestamp: u32,
564 ) {
565 let ptr = pointer / ALIGN;
566 let meta_page = unsafe { self.meta.get_unchecked_mut(address_space) };
568
569 meta_page.set(ptr, AccessMetadata::new(timestamp, BLOCK_SIZE as u8, 0));
571
572 for i in 1..(BLOCK_SIZE / ALIGN) {
574 meta_page.set(ptr + i, AccessMetadata::new(0, 0, i as u8));
575 }
576 }
577
578 pub(crate) fn add_split_record(&mut self, header: AccessRecordHeader) {
579 if header.block_size == header.lowest_block_size {
580 return;
581 }
582 let data_slice = unsafe {
589 self.data.memory.get_u8_slice(
590 header.address_space,
591 (header.pointer * header.type_size) as usize,
592 (header.block_size * header.type_size) as usize,
593 )
594 };
595
596 let record_mut = self
597 .access_adapter_records
598 .alloc(AccessLayout::from_record_header(&header));
599 *record_mut.header = header;
600 record_mut.data.copy_from_slice(data_slice);
601 }
603
604 pub(crate) fn add_merge_record(
606 &mut self,
607 header: AccessRecordHeader,
608 data_slice: &[u8],
609 prev_ts: &[u32],
610 ) {
611 if header.block_size == header.lowest_block_size {
612 return;
613 }
614
615 let record_mut = self
616 .access_adapter_records
617 .alloc(AccessLayout::from_record_header(&header));
618 *record_mut.header = header;
619 record_mut.header.timestamp_and_mask |= MERGE_AND_NOT_SPLIT_FLAG;
620 record_mut.data.copy_from_slice(data_slice);
621 record_mut.timestamps.copy_from_slice(prev_ts);
622 }
623
624 #[inline(always)]
627 #[allow(clippy::type_complexity)]
628 fn calculate_splits_and_merges<const BLOCK_SIZE: usize, const ALIGN: usize>(
629 &mut self,
630 address_space: usize,
631 pointer: usize,
632 ) -> Option<(Vec<(usize, usize)>, (usize, usize))> {
633 let (start_ptr, block_meta) = self.get_block_metadata::<ALIGN>(address_space, pointer);
635 if block_meta.block_size() == BLOCK_SIZE as u8 && start_ptr == pointer as u32 {
636 return None;
637 }
638
639 let mut splits_buf = [(0usize, 0usize); MAX_SEGMENTS];
641 let mut splits_count = 0;
642 let mut current_ptr = pointer;
643 let end_ptr = pointer + BLOCK_SIZE;
644
645 while current_ptr < end_ptr {
646 let (start_ptr, block_metadata) =
647 self.get_block_metadata::<ALIGN>(address_space, current_ptr);
648
649 if block_metadata.block_size() == 0 {
650 current_ptr += ALIGN;
651 continue;
652 }
653
654 if block_metadata.block_size() > ALIGN as u8 {
655 unsafe {
658 *splits_buf.get_unchecked_mut(splits_count) =
659 (start_ptr as usize, block_metadata.block_size() as usize);
660 }
661 splits_count += 1;
662 }
663
664 current_ptr = start_ptr as usize + block_metadata.block_size() as usize;
666 }
667
668 let merge = (pointer, BLOCK_SIZE);
669
670 Some((splits_buf[..splits_count].to_vec(), merge))
671 }
672
673 #[inline(always)]
674 fn split_by_meta<T: Copy, const MIN_BLOCK_SIZE: usize>(
675 &mut self,
676 start_ptr: u32,
677 timestamp: u32,
678 block_size: u8,
679 address_space: usize,
680 ) {
681 if block_size == MIN_BLOCK_SIZE as u8 {
682 return;
683 }
684 let begin = start_ptr as usize / MIN_BLOCK_SIZE;
685 let meta_page = unsafe { self.meta.get_unchecked_mut(address_space) };
689
690 for i in 0..(block_size as usize / MIN_BLOCK_SIZE) {
691 meta_page.set(
693 begin + i,
694 AccessMetadata::new(timestamp, MIN_BLOCK_SIZE as u8, 0),
695 );
696 }
697 self.add_split_record(AccessRecordHeader {
698 timestamp_and_mask: timestamp,
699 address_space: address_space as u32,
700 pointer: start_ptr,
701 block_size: block_size as u32,
702 lowest_block_size: MIN_BLOCK_SIZE as u32,
703 type_size: size_of::<T>() as u32,
704 });
705 }
706
707 #[inline(always)]
711 fn prev_access_time<T: Copy, const BLOCK_SIZE: usize, const ALIGN: usize>(
712 &mut self,
713 address_space: usize,
714 pointer: usize,
715 prev_values: &[T; BLOCK_SIZE],
716 ) -> u32 {
717 debug_assert_eq!(ALIGN, self.data.memory.config[address_space].min_block_size);
718 debug_assert_eq!(
722 unsafe {
723 self.data
724 .memory
725 .config
726 .get_unchecked(address_space)
727 .layout
728 .size()
729 },
730 size_of::<T>()
731 );
732 let result = if let Some((splits, (merge_ptr, merge_size))) =
734 self.calculate_splits_and_merges::<BLOCK_SIZE, ALIGN>(address_space, pointer)
735 {
736 for (split_ptr, split_size) in splits {
738 let (_, block_metadata) =
739 self.get_block_metadata::<ALIGN>(address_space, split_ptr);
740 let timestamp = block_metadata.timestamp();
741 self.split_by_meta::<T, ALIGN>(
742 split_ptr as u32,
743 timestamp,
744 split_size as u8,
745 address_space,
746 );
747 }
748
749 let mut prev_ts_buf = [0u32; MAX_SEGMENTS];
751
752 let mut max_timestamp = INITIAL_TIMESTAMP;
753
754 let mut ptr = merge_ptr;
755 let end_ptr = merge_ptr + merge_size;
756 let mut seg_idx = 0;
757 while ptr < end_ptr {
758 let (_, block_metadata) = self.get_block_metadata::<ALIGN>(address_space, ptr);
759
760 let timestamp = if block_metadata.block_size() > 0 {
761 block_metadata.timestamp()
762 } else {
763 self.handle_uninitialized_memory::<T, ALIGN>(address_space, ptr);
764 INITIAL_TIMESTAMP
765 };
766
767 unsafe {
770 *prev_ts_buf.get_unchecked_mut(seg_idx) = timestamp;
771 }
772 max_timestamp = max_timestamp.max(timestamp);
773 ptr += ALIGN;
774 seg_idx += 1;
775 }
776
777 self.add_merge_record(
779 AccessRecordHeader {
780 timestamp_and_mask: max_timestamp,
781 address_space: address_space as u32,
782 pointer: merge_ptr as u32,
783 block_size: merge_size as u32,
784 lowest_block_size: ALIGN as u32,
785 type_size: size_of::<T>() as u32,
786 },
787 unsafe { slice_as_bytes(prev_values) },
789 &prev_ts_buf[..seg_idx],
790 );
791
792 max_timestamp
793 } else {
794 self.get_timestamp::<ALIGN>(address_space, pointer)
795 };
796
797 self.set_meta_block::<BLOCK_SIZE, ALIGN>(address_space, pointer, self.timestamp);
799 result
800 }
801
802 #[inline(always)]
804 fn handle_uninitialized_memory<T: Copy, const ALIGN: usize>(
805 &mut self,
806 address_space: usize,
807 pointer: usize,
808 ) {
809 if self.initial_block_size >= ALIGN {
810 let segment_index = pointer / ALIGN;
812 let block_start = segment_index & !(self.initial_block_size / ALIGN - 1);
813 let start_ptr = (block_start * ALIGN) as u32;
814 self.split_by_meta::<T, ALIGN>(
815 start_ptr,
816 INITIAL_TIMESTAMP,
817 self.initial_block_size as u8,
818 address_space,
819 );
820 } else {
821 debug_assert_eq!(self.initial_block_size, 1);
823 self.add_merge_record(
824 AccessRecordHeader {
825 timestamp_and_mask: INITIAL_TIMESTAMP,
826 address_space: address_space as u32,
827 pointer: pointer as u32,
828 block_size: ALIGN as u32,
829 lowest_block_size: self.initial_block_size as u32,
830 type_size: size_of::<T>() as u32,
831 },
832 &INITIAL_CELL_BUFFER[..ALIGN],
833 &INITIAL_TIMESTAMP_BUFFER[..ALIGN],
834 );
835 }
836 }
837
838 #[inline(always)]
860 pub unsafe fn read<T, const BLOCK_SIZE: usize, const ALIGN: usize>(
861 &mut self,
862 address_space: u32,
863 pointer: u32,
864 ) -> (u32, [T; BLOCK_SIZE])
865 where
866 T: Copy + Debug,
867 {
868 self.assert_alignment(BLOCK_SIZE, ALIGN, address_space, pointer);
869 let values = self.data.read(address_space, pointer);
870 let t_prev = self.prev_access_time::<T, BLOCK_SIZE, ALIGN>(
871 address_space as usize,
872 pointer as usize,
873 &values,
874 );
875 self.timestamp += 1;
876
877 (t_prev, values)
878 }
879
880 #[inline(always)]
902 pub unsafe fn write<T, const BLOCK_SIZE: usize, const ALIGN: usize>(
903 &mut self,
904 address_space: u32,
905 pointer: u32,
906 values: [T; BLOCK_SIZE],
907 ) -> (u32, [T; BLOCK_SIZE])
908 where
909 T: Copy + Debug,
910 {
911 self.assert_alignment(BLOCK_SIZE, ALIGN, address_space, pointer);
912 let values_prev = self.data.read(address_space, pointer);
913 let t_prev = self.prev_access_time::<T, BLOCK_SIZE, ALIGN>(
914 address_space as usize,
915 pointer as usize,
916 &values_prev,
917 );
918 self.data.write(address_space, pointer, values);
919 self.timestamp += 1;
920
921 (t_prev, values_prev)
922 }
923
924 pub fn increment_timestamp(&mut self) {
925 self.timestamp += 1;
926 }
927
928 pub fn increment_timestamp_by(&mut self, amount: u32) {
929 self.timestamp += amount;
930 }
931
932 pub fn timestamp(&self) -> u32 {
933 self.timestamp
934 }
935
936 #[instrument(name = "memory_finalize", skip_all)]
938 pub fn finalize<F: Field>(&mut self, is_persistent: bool) -> TouchedMemory<F> {
939 let touched_blocks = self.touched_blocks();
940
941 match is_persistent {
942 false => TouchedMemory::Volatile(
943 self.touched_blocks_to_equipartition::<F, 1>(touched_blocks),
944 ),
945 true => TouchedMemory::Persistent(
946 self.touched_blocks_to_equipartition::<F, CHUNK>(touched_blocks),
947 ),
948 }
949 }
950
951 fn touched_blocks(&self) -> Vec<(Address, AccessMetadata)> {
953 assert_eq!(self.meta.len(), self.min_block_size.len());
954 self.meta
955 .par_iter()
956 .zip(self.min_block_size.par_iter())
957 .enumerate()
958 .flat_map(|(addr_space, (meta_page, &align))| {
959 meta_page
960 .par_iter()
961 .filter_map(move |(idx, metadata)| {
962 let ptr = idx as u32 * align;
963 if metadata.offset_to_start == 0 && metadata.block_size() != 0 {
964 Some(((addr_space as u32, ptr), metadata))
965 } else {
966 None
967 }
968 })
969 .collect::<Vec<_>>()
970 })
971 .collect()
972 }
973
974 fn touched_blocks_to_equipartition<F: Field, const CHUNK: usize>(
977 &mut self,
978 touched_blocks: Vec<((u32, u32), AccessMetadata)>,
979 ) -> TimestampedEquipartition<F, CHUNK> {
980 let mut final_memory = Vec::new();
982
983 debug_assert!(touched_blocks.is_sorted_by_key(|(addr, _)| addr));
984 self.handle_touched_blocks::<F, CHUNK>(&mut final_memory, touched_blocks);
985
986 debug_assert!(final_memory.is_sorted_by_key(|(key, _)| *key));
987 final_memory
988 }
989
990 fn handle_touched_blocks<F: Field, const CHUNK: usize>(
991 &mut self,
992 final_memory: &mut Vec<((u32, u32), TimestampedValues<F, CHUNK>)>,
993 touched_blocks: Vec<((u32, u32), AccessMetadata)>,
994 ) {
995 let mut current_values = vec![0u8; MAX_CELL_BYTE_SIZE * CHUNK];
996 let mut current_cnt = 0;
997 let mut current_address = MemoryAddress::new(0, 0);
998 let mut current_timestamps = vec![0; CHUNK];
999 for ((addr_space, ptr), access_metadata) in touched_blocks {
1000 let addr_space_config =
1002 unsafe { *self.data.memory.config.get_unchecked(addr_space as usize) };
1003 let min_block_size = addr_space_config.min_block_size;
1004 let cell_size = addr_space_config.layout.size();
1005 let timestamp = access_metadata.timestamp();
1006 let block_size = access_metadata.block_size();
1007 assert!(
1008 current_cnt == 0
1009 || (current_address.address_space == addr_space
1010 && current_address.pointer + current_cnt as u32 == ptr),
1011 "The union of all touched blocks must consist of blocks with sizes divisible by `CHUNK`"
1012 );
1013 debug_assert!(block_size >= min_block_size as u8);
1014 debug_assert!(ptr % min_block_size as u32 == 0);
1015
1016 if current_cnt == 0 {
1017 assert_eq!(
1018 ptr & (CHUNK as u32 - 1),
1019 0,
1020 "The union of all touched blocks must consist of `CHUNK`-aligned blocks"
1021 );
1022 current_address = MemoryAddress::new(addr_space, ptr);
1023 }
1024
1025 if block_size > min_block_size as u8 {
1026 self.add_split_record(AccessRecordHeader {
1027 timestamp_and_mask: timestamp,
1028 address_space: addr_space,
1029 pointer: ptr,
1030 block_size: block_size as u32,
1031 lowest_block_size: min_block_size as u32,
1032 type_size: cell_size as u32,
1033 });
1034 }
1035 if min_block_size > CHUNK {
1036 assert_eq!(current_cnt, 0);
1037 for i in (0..block_size as u32).step_by(min_block_size) {
1038 self.add_split_record(AccessRecordHeader {
1039 timestamp_and_mask: timestamp,
1040 address_space: addr_space,
1041 pointer: ptr + i,
1042 block_size: min_block_size as u32,
1043 lowest_block_size: CHUNK as u32,
1044 type_size: cell_size as u32,
1045 });
1046 }
1047 let values = unsafe {
1049 self.data.memory.get_u8_slice(
1050 addr_space,
1051 ptr as usize * cell_size,
1052 block_size as usize * cell_size,
1053 )
1054 };
1055 for i in (0..block_size as u32).step_by(CHUNK) {
1056 final_memory.push((
1057 (addr_space, ptr + i),
1058 TimestampedValues {
1059 timestamp,
1060 values: from_fn(|j| {
1061 let byte_idx = (i as usize + j) * cell_size;
1062 unsafe {
1065 addr_space_config
1066 .layout
1067 .to_field(&values[byte_idx..byte_idx + cell_size])
1068 }
1069 }),
1070 },
1071 ));
1072 }
1073 } else {
1074 for i in 0..block_size as u32 {
1075 let cell_data = unsafe {
1077 self.data.memory.get_u8_slice(
1078 addr_space,
1079 (ptr + i) as usize * cell_size,
1080 cell_size,
1081 )
1082 };
1083 current_values[current_cnt * cell_size..current_cnt * cell_size + cell_size]
1084 .copy_from_slice(cell_data);
1085 if current_cnt & (min_block_size - 1) == 0 {
1086 unsafe {
1088 *current_timestamps.get_unchecked_mut(current_cnt / min_block_size) =
1089 timestamp;
1090 }
1091 }
1092 current_cnt += 1;
1093 if current_cnt == CHUNK {
1094 let timestamp = *current_timestamps[..CHUNK / min_block_size]
1095 .iter()
1096 .max()
1097 .unwrap();
1098 self.add_merge_record(
1099 AccessRecordHeader {
1100 timestamp_and_mask: timestamp,
1101 address_space: addr_space,
1102 pointer: current_address.pointer,
1103 block_size: CHUNK as u32,
1104 lowest_block_size: min_block_size as u32,
1105 type_size: cell_size as u32,
1106 },
1107 ¤t_values[..CHUNK * cell_size],
1108 ¤t_timestamps[..CHUNK / min_block_size],
1109 );
1110 final_memory.push((
1111 (current_address.address_space, current_address.pointer),
1112 TimestampedValues {
1113 timestamp,
1114 values: from_fn(|i| unsafe {
1115 addr_space_config.layout.to_field(
1117 ¤t_values[i * cell_size..i * cell_size + cell_size],
1118 )
1119 }),
1120 },
1121 ));
1122 current_address.pointer += current_cnt as u32;
1123 current_cnt = 0;
1124 }
1125 }
1126 }
1127 }
1128 assert_eq!(current_cnt, 0, "The union of all touched blocks must consist of blocks with sizes divisible by `CHUNK`");
1129 }
1130
1131 pub fn address_space_alignment(&self) -> Vec<u8> {
1132 self.min_block_size
1133 .iter()
1134 .map(|&x| log2_strict_usize(x as usize) as u8)
1135 .collect()
1136 }
1137}