1use std::{
2 array,
3 collections::BTreeMap,
4 iter,
5 marker::PhantomData,
6 mem,
7 sync::{Arc, Mutex},
8};
9
10use getset::{Getters, MutGetters};
11use openvm_circuit_primitives::{
12 assert_less_than::{AssertLtSubAir, LessThanAuxCols},
13 is_zero::IsZeroSubAir,
14 utils::next_power_of_two_or_zero,
15 var_range::{SharedVariableRangeCheckerChip, VariableRangeCheckerBus},
16 TraceSubRowGenerator,
17};
18use openvm_stark_backend::{
19 config::{Domain, StarkGenericConfig},
20 interaction::PermutationCheckBus,
21 p3_commit::PolynomialSpace,
22 p3_field::PrimeField32,
23 p3_maybe_rayon::prelude::{IntoParallelIterator, ParallelIterator},
24 p3_util::{log2_ceil_usize, log2_strict_usize},
25 prover::types::AirProofInput,
26 AirRef, Chip, ChipUsageGetter,
27};
28use serde::{Deserialize, Serialize};
29
30use self::interface::MemoryInterface;
31use super::{
32 paged_vec::{AddressMap, PAGE_SIZE},
33 volatile::VolatileBoundaryChip,
34};
35use crate::{
36 arch::{hasher::HasherChip, MemoryConfig},
37 system::memory::{
38 adapter::AccessAdapterInventory,
39 dimensions::MemoryDimensions,
40 merkle::{MemoryMerkleChip, SerialReceiver},
41 offline::{MemoryRecord, OfflineMemory, INITIAL_TIMESTAMP},
42 offline_checker::{
43 MemoryBaseAuxCols, MemoryBridge, MemoryBus, MemoryReadAuxCols,
44 MemoryReadOrImmediateAuxCols, MemoryWriteAuxCols, AUX_LEN,
45 },
46 online::{Memory, MemoryLogEntry},
47 persistent::PersistentBoundaryChip,
48 tree::MemoryNode,
49 },
50};
51
52pub mod dimensions;
53pub mod interface;
54
55pub const CHUNK: usize = 8;
56pub const MERKLE_AIR_OFFSET: usize = 1;
58pub const BOUNDARY_AIR_OFFSET: usize = 0;
60
61#[repr(C)]
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63pub struct RecordId(pub usize);
64
65pub type MemoryImage<F> = AddressMap<F, PAGE_SIZE>;
66
67#[repr(C)]
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub struct TimestampedValues<T, const N: usize> {
70 pub timestamp: u32,
71 pub values: [T; N],
72}
73
74pub type TimestampedEquipartition<F, const N: usize> =
81 BTreeMap<(u32, u32), TimestampedValues<F, N>>;
82
83pub type Equipartition<F, const N: usize> = BTreeMap<(u32, u32), [F; N]>;
90
91#[derive(Getters, MutGetters)]
92pub struct MemoryController<F> {
93 pub memory_bus: MemoryBus,
94 pub interface_chip: MemoryInterface<F>,
95 #[getset(get = "pub")]
96 pub(crate) mem_config: MemoryConfig,
97 pub range_checker: SharedVariableRangeCheckerChip,
98 range_checker_bus: VariableRangeCheckerBus,
100 memory: Memory<F>,
102 offline_memory: Arc<Mutex<OfflineMemory<F>>>,
104 pub access_adapters: AccessAdapterInventory<F>,
105 final_state: Option<FinalState<F>>,
107}
108
109#[allow(clippy::large_enum_variant)]
110#[derive(Debug)]
111enum FinalState<F> {
112 Volatile(VolatileFinalState<F>),
113 #[allow(dead_code)]
114 Persistent(PersistentFinalState<F>),
115}
116#[derive(Debug, Default)]
117struct VolatileFinalState<F> {
118 _marker: PhantomData<F>,
119}
120#[allow(dead_code)]
121#[derive(Debug)]
122struct PersistentFinalState<F> {
123 final_memory: Equipartition<F, CHUNK>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127pub enum MemoryTraceHeights {
128 Volatile(VolatileMemoryTraceHeights),
129 Persistent(PersistentMemoryTraceHeights),
130}
131
132impl MemoryTraceHeights {
133 fn flatten(&self) -> Vec<usize> {
134 match self {
135 MemoryTraceHeights::Volatile(oh) => oh.flatten(),
136 MemoryTraceHeights::Persistent(oh) => oh.flatten(),
137 }
138 }
139
140 pub fn round_to_next_power_of_two(&mut self) {
142 match self {
143 MemoryTraceHeights::Volatile(oh) => oh.round_to_next_power_of_two(),
144 MemoryTraceHeights::Persistent(oh) => oh.round_to_next_power_of_two(),
145 }
146 }
147
148 pub fn round_to_next_power_of_two_or_zero(&mut self) {
150 match self {
151 MemoryTraceHeights::Volatile(oh) => oh.round_to_next_power_of_two_or_zero(),
152 MemoryTraceHeights::Persistent(oh) => oh.round_to_next_power_of_two_or_zero(),
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158pub struct VolatileMemoryTraceHeights {
159 pub boundary: usize,
160 pub access_adapters: Vec<usize>,
161}
162
163impl VolatileMemoryTraceHeights {
164 pub fn flatten(&self) -> Vec<usize> {
165 iter::once(self.boundary)
166 .chain(self.access_adapters.iter().copied())
167 .collect()
168 }
169
170 fn round_to_next_power_of_two(&mut self) {
171 self.boundary = self.boundary.next_power_of_two();
172 self.access_adapters
173 .iter_mut()
174 .for_each(|v| *v = v.next_power_of_two());
175 }
176
177 fn round_to_next_power_of_two_or_zero(&mut self) {
178 self.boundary = next_power_of_two_or_zero(self.boundary);
179 self.access_adapters
180 .iter_mut()
181 .for_each(|v| *v = next_power_of_two_or_zero(*v));
182 }
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
186pub struct PersistentMemoryTraceHeights {
187 boundary: usize,
188 merkle: usize,
189 access_adapters: Vec<usize>,
190}
191impl PersistentMemoryTraceHeights {
192 pub fn flatten(&self) -> Vec<usize> {
193 vec![self.boundary, self.merkle]
194 .into_iter()
195 .chain(self.access_adapters.iter().copied())
196 .collect()
197 }
198
199 fn round_to_next_power_of_two(&mut self) {
200 self.boundary = self.boundary.next_power_of_two();
201 self.merkle = self.merkle.next_power_of_two();
202 self.access_adapters
203 .iter_mut()
204 .for_each(|v| *v = v.next_power_of_two());
205 }
206
207 fn round_to_next_power_of_two_or_zero(&mut self) {
208 self.boundary = next_power_of_two_or_zero(self.boundary);
209 self.merkle = next_power_of_two_or_zero(self.merkle);
210 self.access_adapters
211 .iter_mut()
212 .for_each(|v| *v = next_power_of_two_or_zero(*v));
213 }
214}
215
216impl<F: PrimeField32> MemoryController<F> {
217 pub fn continuation_enabled(&self) -> bool {
218 match &self.interface_chip {
219 MemoryInterface::Volatile { .. } => false,
220 MemoryInterface::Persistent { .. } => true,
221 }
222 }
223 pub fn with_volatile_memory(
224 memory_bus: MemoryBus,
225 mem_config: MemoryConfig,
226 range_checker: SharedVariableRangeCheckerChip,
227 ) -> Self {
228 let range_checker_bus = range_checker.bus();
229 let initial_memory = AddressMap::from_mem_config(&mem_config);
230 assert!(mem_config.pointer_max_bits <= F::bits() - 2);
231 assert!(mem_config.as_height < F::bits() - 2);
232 let addr_space_max_bits = log2_ceil_usize(
233 (mem_config.as_offset + 2u32.pow(mem_config.as_height as u32)) as usize,
234 );
235 Self {
236 memory_bus,
237 mem_config,
238 interface_chip: MemoryInterface::Volatile {
239 boundary_chip: VolatileBoundaryChip::new(
240 memory_bus,
241 addr_space_max_bits,
242 mem_config.pointer_max_bits,
243 range_checker.clone(),
244 ),
245 },
246 memory: Memory::new(&mem_config),
247 offline_memory: Arc::new(Mutex::new(OfflineMemory::new(
248 initial_memory,
249 1,
250 memory_bus,
251 range_checker.clone(),
252 mem_config,
253 ))),
254 access_adapters: AccessAdapterInventory::new(
255 range_checker.clone(),
256 memory_bus,
257 mem_config.clk_max_bits,
258 mem_config.max_access_adapter_n,
259 ),
260 range_checker,
261 range_checker_bus,
262 final_state: None,
263 }
264 }
265
266 pub fn with_persistent_memory(
270 memory_bus: MemoryBus,
271 mem_config: MemoryConfig,
272 range_checker: SharedVariableRangeCheckerChip,
273 merkle_bus: PermutationCheckBus,
274 compression_bus: PermutationCheckBus,
275 ) -> Self {
276 assert_eq!(mem_config.as_offset, 1);
277 let memory_dims = MemoryDimensions {
278 as_height: mem_config.as_height,
279 address_height: mem_config.pointer_max_bits - log2_strict_usize(CHUNK),
280 as_offset: 1,
281 };
282 let range_checker_bus = range_checker.bus();
283 let interface_chip = MemoryInterface::Persistent {
284 boundary_chip: PersistentBoundaryChip::new(
285 memory_dims,
286 memory_bus,
287 merkle_bus,
288 compression_bus,
289 ),
290 merkle_chip: MemoryMerkleChip::new(memory_dims, merkle_bus, compression_bus),
291 initial_memory: AddressMap::from_mem_config(&mem_config),
292 };
293 Self {
294 memory_bus,
295 mem_config,
296 interface_chip,
297 memory: Memory::new(&mem_config), offline_memory: Arc::new(Mutex::new(OfflineMemory::new(
299 AddressMap::from_mem_config(&mem_config),
300 CHUNK,
301 memory_bus,
302 range_checker.clone(),
303 mem_config,
304 ))),
305 access_adapters: AccessAdapterInventory::new(
306 range_checker.clone(),
307 memory_bus,
308 mem_config.clk_max_bits,
309 mem_config.max_access_adapter_n,
310 ),
311 range_checker,
312 range_checker_bus,
313 final_state: None,
314 }
315 }
316
317 pub fn memory_image(&self) -> &MemoryImage<F> {
318 &self.memory.data
319 }
320
321 pub fn set_override_trace_heights(&mut self, overridden_heights: MemoryTraceHeights) {
322 match &mut self.interface_chip {
323 MemoryInterface::Volatile { boundary_chip } => match overridden_heights {
324 MemoryTraceHeights::Volatile(oh) => {
325 boundary_chip.set_overridden_height(oh.boundary);
326 self.access_adapters
327 .set_override_trace_heights(oh.access_adapters);
328 }
329 _ => panic!("Expect overridden_heights to be MemoryTraceHeights::Volatile"),
330 },
331 MemoryInterface::Persistent {
332 boundary_chip,
333 merkle_chip,
334 ..
335 } => match overridden_heights {
336 MemoryTraceHeights::Persistent(oh) => {
337 boundary_chip.set_overridden_height(oh.boundary);
338 merkle_chip.set_overridden_height(oh.merkle);
339 self.access_adapters
340 .set_override_trace_heights(oh.access_adapters);
341 }
342 _ => panic!("Expect overridden_heights to be MemoryTraceHeights::Persistent"),
343 },
344 }
345 }
346
347 pub fn set_initial_memory(&mut self, memory: MemoryImage<F>) {
348 if self.timestamp() > INITIAL_TIMESTAMP + 1 {
349 panic!("Cannot set initial memory after first timestamp");
350 }
351 let mut offline_memory = self.offline_memory.lock().unwrap();
352 offline_memory.set_initial_memory(memory.clone(), self.mem_config);
353
354 self.memory = Memory::from_image(memory.clone(), self.mem_config.access_capacity);
355
356 match &mut self.interface_chip {
357 MemoryInterface::Volatile { .. } => {
358 assert!(
359 memory.is_empty(),
360 "Cannot set initial memory for volatile memory"
361 );
362 }
363 MemoryInterface::Persistent { initial_memory, .. } => {
364 *initial_memory = memory;
365 }
366 }
367 }
368
369 pub fn memory_bridge(&self) -> MemoryBridge {
370 MemoryBridge::new(
371 self.memory_bus,
372 self.mem_config.clk_max_bits,
373 self.range_checker_bus,
374 )
375 }
376
377 pub fn read_cell(&mut self, address_space: F, pointer: F) -> (RecordId, F) {
378 let (record_id, [data]) = self.read(address_space, pointer);
379 (record_id, data)
380 }
381
382 pub fn read<const N: usize>(&mut self, address_space: F, pointer: F) -> (RecordId, [F; N]) {
383 let address_space_u32 = address_space.as_canonical_u32();
384 let ptr_u32 = pointer.as_canonical_u32();
385 assert!(
386 address_space == F::ZERO || ptr_u32 < (1 << self.mem_config.pointer_max_bits),
387 "memory out of bounds: {ptr_u32:?}",
388 );
389
390 let (record_id, values) = self.memory.read::<N>(address_space_u32, ptr_u32);
391
392 (record_id, values)
393 }
394
395 pub fn unsafe_read_cell(&self, addr_space: F, ptr: F) -> F {
399 self.unsafe_read::<1>(addr_space, ptr)[0]
400 }
401
402 pub fn unsafe_read<const N: usize>(&self, addr_space: F, ptr: F) -> [F; N] {
406 let addr_space = addr_space.as_canonical_u32();
407 let ptr = ptr.as_canonical_u32();
408 array::from_fn(|i| self.memory.get(addr_space, ptr + i as u32))
409 }
410
411 pub fn write_cell(&mut self, address_space: F, pointer: F, data: F) -> (RecordId, F) {
415 let (record_id, [data]) = self.write(address_space, pointer, [data]);
416 (record_id, data)
417 }
418
419 pub fn write<const N: usize>(
420 &mut self,
421 address_space: F,
422 pointer: F,
423 data: [F; N],
424 ) -> (RecordId, [F; N]) {
425 assert_ne!(address_space, F::ZERO);
426 let address_space_u32 = address_space.as_canonical_u32();
427 let ptr_u32 = pointer.as_canonical_u32();
428 assert!(
429 ptr_u32 < (1 << self.mem_config.pointer_max_bits),
430 "memory out of bounds: {ptr_u32:?}",
431 );
432
433 self.memory.write(address_space_u32, ptr_u32, data)
434 }
435
436 pub fn aux_cols_factory(&self) -> MemoryAuxColsFactory<F> {
437 let range_bus = self.range_checker.bus();
438 MemoryAuxColsFactory {
439 range_checker: self.range_checker.clone(),
440 timestamp_lt_air: AssertLtSubAir::new(range_bus, self.mem_config.clk_max_bits),
441 _marker: Default::default(),
442 }
443 }
444
445 pub fn increment_timestamp(&mut self) {
446 self.memory.increment_timestamp_by(1);
447 }
448
449 pub fn increment_timestamp_by(&mut self, change: u32) {
450 self.memory.increment_timestamp_by(change);
451 }
452
453 pub fn timestamp(&self) -> u32 {
454 self.memory.timestamp()
455 }
456
457 fn replay_access_log(&mut self) {
458 let log = mem::take(&mut self.memory.log);
459 if log.is_empty() {
460 tracing::debug!("skipping replay_access_log");
464 return;
465 }
466
467 let mut offline_memory = self.offline_memory.lock().unwrap();
468 offline_memory.set_log_capacity(log.len());
469
470 for entry in log {
471 Self::replay_access(
472 entry,
473 &mut offline_memory,
474 &mut self.interface_chip,
475 &mut self.access_adapters,
476 );
477 }
478 }
479
480 pub fn replay_access(
483 entry: MemoryLogEntry<F>,
484 offline_memory: &mut OfflineMemory<F>,
485 interface_chip: &mut MemoryInterface<F>,
486 adapter_records: &mut AccessAdapterInventory<F>,
487 ) {
488 match entry {
489 MemoryLogEntry::Read {
490 address_space,
491 pointer,
492 len,
493 } => {
494 if address_space != 0 {
495 interface_chip.touch_range(address_space, pointer, len as u32);
496 }
497 offline_memory.read(address_space, pointer, len, adapter_records);
498 }
499 MemoryLogEntry::Write {
500 address_space,
501 pointer,
502 data,
503 } => {
504 if address_space != 0 {
505 interface_chip.touch_range(address_space, pointer, data.len() as u32);
506 }
507 offline_memory.write(address_space, pointer, data, adapter_records);
508 }
509 MemoryLogEntry::IncrementTimestampBy(amount) => {
510 offline_memory.increment_timestamp_by(amount);
511 }
512 };
513 }
514
515 pub fn finalize<H>(&mut self, hasher: Option<&mut H>)
517 where
518 H: HasherChip<CHUNK, F> + Sync + for<'a> SerialReceiver<&'a [F]>,
519 {
520 if self.final_state.is_some() {
521 return;
522 }
523
524 self.replay_access_log();
525 let mut offline_memory = self.offline_memory.lock().unwrap();
526
527 match &mut self.interface_chip {
528 MemoryInterface::Volatile { boundary_chip } => {
529 let final_memory = offline_memory.finalize::<1>(&mut self.access_adapters);
530 boundary_chip.finalize(final_memory);
531 self.final_state = Some(FinalState::Volatile(VolatileFinalState::default()));
532 }
533 MemoryInterface::Persistent {
534 merkle_chip,
535 boundary_chip,
536 initial_memory,
537 } => {
538 let hasher = hasher.unwrap();
539 let final_partition = offline_memory.finalize::<CHUNK>(&mut self.access_adapters);
540
541 boundary_chip.finalize(initial_memory, &final_partition, hasher);
542 let final_memory_values = final_partition
543 .into_par_iter()
544 .map(|(key, value)| (key, value.values))
545 .collect();
546 let initial_node = MemoryNode::tree_from_memory(
547 merkle_chip.air.memory_dimensions,
548 initial_memory,
549 hasher,
550 );
551 merkle_chip.finalize(&initial_node, &final_memory_values, hasher);
552 self.final_state = Some(FinalState::Persistent(PersistentFinalState {
553 final_memory: final_memory_values.clone(),
554 }));
555 }
556 };
557 }
558
559 pub fn generate_air_proof_inputs<SC: StarkGenericConfig>(self) -> Vec<AirProofInput<SC>>
560 where
561 Domain<SC>: PolynomialSpace<Val = F>,
562 {
563 let mut ret = Vec::new();
564
565 let Self {
566 interface_chip,
567 access_adapters,
568 ..
569 } = self;
570 match interface_chip {
571 MemoryInterface::Volatile { boundary_chip } => {
572 ret.push(boundary_chip.generate_air_proof_input());
573 }
574 MemoryInterface::Persistent {
575 merkle_chip,
576 boundary_chip,
577 ..
578 } => {
579 debug_assert_eq!(ret.len(), BOUNDARY_AIR_OFFSET);
580 ret.push(boundary_chip.generate_air_proof_input());
581 debug_assert_eq!(ret.len(), MERKLE_AIR_OFFSET);
582 ret.push(merkle_chip.generate_air_proof_input());
583 }
584 }
585 ret.extend(access_adapters.generate_air_proof_inputs());
586 ret
587 }
588
589 pub fn airs<SC: StarkGenericConfig>(&self) -> Vec<AirRef<SC>>
590 where
591 Domain<SC>: PolynomialSpace<Val = F>,
592 {
593 let mut airs = Vec::<AirRef<SC>>::new();
594
595 match &self.interface_chip {
596 MemoryInterface::Volatile { boundary_chip } => {
597 debug_assert_eq!(airs.len(), BOUNDARY_AIR_OFFSET);
598 airs.push(boundary_chip.air())
599 }
600 MemoryInterface::Persistent {
601 boundary_chip,
602 merkle_chip,
603 ..
604 } => {
605 debug_assert_eq!(airs.len(), BOUNDARY_AIR_OFFSET);
606 airs.push(boundary_chip.air());
607 debug_assert_eq!(airs.len(), MERKLE_AIR_OFFSET);
608 airs.push(merkle_chip.air());
609 }
610 }
611 airs.extend(self.access_adapters.airs());
612
613 airs
614 }
615
616 pub fn num_airs(&self) -> usize {
618 let mut num_airs = 1;
619 if self.continuation_enabled() {
620 num_airs += 1;
621 }
622 num_airs += self.access_adapters.num_access_adapters();
623 num_airs
624 }
625
626 pub fn air_names(&self) -> Vec<String> {
627 let mut air_names = vec!["Boundary".to_string()];
628 if self.continuation_enabled() {
629 air_names.push("Merkle".to_string());
630 }
631 air_names.extend(self.access_adapters.air_names());
632 air_names
633 }
634
635 pub fn current_trace_heights(&self) -> Vec<usize> {
636 self.get_memory_trace_heights().flatten()
637 }
638
639 pub fn get_memory_trace_heights(&self) -> MemoryTraceHeights {
640 let access_adapters = self.access_adapters.get_heights();
641 match &self.interface_chip {
642 MemoryInterface::Volatile { boundary_chip } => {
643 MemoryTraceHeights::Volatile(VolatileMemoryTraceHeights {
644 boundary: boundary_chip.current_trace_height(),
645 access_adapters,
646 })
647 }
648 MemoryInterface::Persistent {
649 boundary_chip,
650 merkle_chip,
651 ..
652 } => MemoryTraceHeights::Persistent(PersistentMemoryTraceHeights {
653 boundary: boundary_chip.current_trace_height(),
654 merkle: merkle_chip.current_trace_height(),
655 access_adapters,
656 }),
657 }
658 }
659
660 pub fn get_dummy_memory_trace_heights(&self) -> MemoryTraceHeights {
661 let access_adapters = vec![1; self.access_adapters.num_access_adapters()];
662 match &self.interface_chip {
663 MemoryInterface::Volatile { .. } => {
664 MemoryTraceHeights::Volatile(VolatileMemoryTraceHeights {
665 boundary: 1,
666 access_adapters,
667 })
668 }
669 MemoryInterface::Persistent { .. } => {
670 MemoryTraceHeights::Persistent(PersistentMemoryTraceHeights {
671 boundary: 1,
672 merkle: 1,
673 access_adapters,
674 })
675 }
676 }
677 }
678
679 pub fn current_trace_cells(&self) -> Vec<usize> {
680 let mut ret = Vec::new();
681 match &self.interface_chip {
682 MemoryInterface::Volatile { boundary_chip } => {
683 ret.push(boundary_chip.current_trace_cells())
684 }
685 MemoryInterface::Persistent {
686 boundary_chip,
687 merkle_chip,
688 ..
689 } => {
690 ret.push(boundary_chip.current_trace_cells());
691 ret.push(merkle_chip.current_trace_cells());
692 }
693 }
694 ret.extend(self.access_adapters.get_cells());
695 ret
696 }
697
698 pub fn offline_memory(&self) -> Arc<Mutex<OfflineMemory<F>>> {
705 self.offline_memory.clone()
706 }
707 pub fn get_memory_logs(&self) -> &Vec<MemoryLogEntry<F>> {
708 &self.memory.log
709 }
710 pub fn set_memory_logs(&mut self, logs: Vec<MemoryLogEntry<F>>) {
711 self.memory.log = logs;
712 }
713 pub fn take_memory_logs(&mut self) -> Vec<MemoryLogEntry<F>> {
714 std::mem::take(&mut self.memory.log)
715 }
716}
717
718pub struct MemoryAuxColsFactory<T> {
719 pub(crate) range_checker: SharedVariableRangeCheckerChip,
720 pub(crate) timestamp_lt_air: AssertLtSubAir,
721 pub(crate) _marker: PhantomData<T>,
722}
723
724impl<F: PrimeField32> MemoryAuxColsFactory<F> {
727 pub fn generate_read_aux(&self, read: &MemoryRecord<F>, buffer: &mut MemoryReadAuxCols<F>) {
728 assert!(
729 !read.address_space.is_zero(),
730 "cannot make `MemoryReadAuxCols` for address space 0"
731 );
732 self.generate_base_aux(read, &mut buffer.base);
733 }
734
735 pub fn generate_read_or_immediate_aux(
736 &self,
737 read: &MemoryRecord<F>,
738 buffer: &mut MemoryReadOrImmediateAuxCols<F>,
739 ) {
740 IsZeroSubAir.generate_subrow(
741 read.address_space,
742 (&mut buffer.is_zero_aux, &mut buffer.is_immediate),
743 );
744 self.generate_base_aux(read, &mut buffer.base);
745 }
746
747 pub fn generate_write_aux<const N: usize>(
748 &self,
749 write: &MemoryRecord<F>,
750 buffer: &mut MemoryWriteAuxCols<F, N>,
751 ) {
752 buffer
753 .prev_data
754 .copy_from_slice(write.prev_data_slice().unwrap());
755 self.generate_base_aux(write, &mut buffer.base);
756 }
757
758 pub fn generate_base_aux(&self, record: &MemoryRecord<F>, buffer: &mut MemoryBaseAuxCols<F>) {
759 buffer.prev_timestamp = F::from_canonical_u32(record.prev_timestamp);
760 self.generate_timestamp_lt(
761 record.prev_timestamp,
762 record.timestamp,
763 &mut buffer.timestamp_lt_aux,
764 );
765 }
766
767 fn generate_timestamp_lt(
768 &self,
769 prev_timestamp: u32,
770 timestamp: u32,
771 buffer: &mut LessThanAuxCols<F, AUX_LEN>,
772 ) {
773 debug_assert!(prev_timestamp < timestamp);
774 self.timestamp_lt_air.generate_subrow(
775 (self.range_checker.as_ref(), prev_timestamp, timestamp),
776 &mut buffer.lower_decomp,
777 );
778 }
779
780 pub fn make_read_aux_cols(&self, read: &MemoryRecord<F>) -> MemoryReadAuxCols<F> {
782 assert!(
783 !read.address_space.is_zero(),
784 "cannot make `MemoryReadAuxCols` for address space 0"
785 );
786 MemoryReadAuxCols::new(
787 read.prev_timestamp,
788 self.generate_timestamp_lt_cols(read.prev_timestamp, read.timestamp),
789 )
790 }
791
792 pub fn make_write_aux_cols<const N: usize>(
794 &self,
795 write: &MemoryRecord<F>,
796 ) -> MemoryWriteAuxCols<F, N> {
797 let prev_data = write.prev_data_slice().unwrap();
798 MemoryWriteAuxCols::new(
799 prev_data.try_into().unwrap(),
800 F::from_canonical_u32(write.prev_timestamp),
801 self.generate_timestamp_lt_cols(write.prev_timestamp, write.timestamp),
802 )
803 }
804
805 fn generate_timestamp_lt_cols(
806 &self,
807 prev_timestamp: u32,
808 timestamp: u32,
809 ) -> LessThanAuxCols<F, AUX_LEN> {
810 debug_assert!(prev_timestamp < timestamp);
811 let mut decomp = [F::ZERO; AUX_LEN];
812 self.timestamp_lt_air.generate_subrow(
813 (self.range_checker.as_ref(), prev_timestamp, timestamp),
814 &mut decomp,
815 );
816 LessThanAuxCols::new(decomp)
817 }
818}
819
820#[cfg(test)]
821mod tests {
822 use openvm_circuit_primitives::var_range::{
823 SharedVariableRangeCheckerChip, VariableRangeCheckerBus,
824 };
825 use openvm_stark_backend::{interaction::BusIndex, p3_field::FieldAlgebra};
826 use openvm_stark_sdk::p3_baby_bear::BabyBear;
827 use rand::{prelude::SliceRandom, thread_rng, Rng};
828
829 use super::MemoryController;
830 use crate::{
831 arch::{testing::MEMORY_BUS, MemoryConfig},
832 system::memory::offline_checker::MemoryBus,
833 };
834
835 const RANGE_CHECKER_BUS: BusIndex = 3;
836
837 #[test]
838 fn test_no_adapter_records_for_singleton_accesses() {
839 type F = BabyBear;
840
841 let memory_bus = MemoryBus::new(MEMORY_BUS);
842 let memory_config = MemoryConfig::default();
843 let range_bus = VariableRangeCheckerBus::new(RANGE_CHECKER_BUS, memory_config.decomp);
844 let range_checker = SharedVariableRangeCheckerChip::new(range_bus);
845
846 let mut memory_controller = MemoryController::with_volatile_memory(
847 memory_bus,
848 memory_config,
849 range_checker.clone(),
850 );
851
852 let mut rng = thread_rng();
853 for _ in 0..1000 {
854 let address_space = F::from_canonical_u32(*[1, 2].choose(&mut rng).unwrap());
855 let pointer =
856 F::from_canonical_u32(rng.gen_range(0..1 << memory_config.pointer_max_bits));
857
858 if rng.gen_bool(0.5) {
859 let data = F::from_canonical_u32(rng.gen_range(0..1 << 30));
860 memory_controller.write(address_space, pointer, [data]);
861 } else {
862 memory_controller.read::<1>(address_space, pointer);
863 }
864 }
865 assert!(memory_controller
866 .access_adapters
867 .get_heights()
868 .iter()
869 .all(|&h| h == 0));
870 }
871}