From cc7f51ad50c7558c15508f26d5cc3699d102668a Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 11 Dec 2025 19:14:46 +0000 Subject: [PATCH 01/19] access adapter boolean flag --- crates/vm/src/arch/config.rs | 78 ++++++++++++++++++- .../arch/execution_mode/metered/memory_ctx.rs | 7 ++ .../src/arch/execution_mode/metered_cost.rs | 7 ++ crates/vm/src/system/memory/adapter/mod.rs | 35 +++++---- crates/vm/src/system/memory/mod.rs | 47 +++++++---- 5 files changed, 141 insertions(+), 33 deletions(-) diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 3ffbfb74e0..17f7d228eb 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -123,6 +123,11 @@ pub const OPENVM_DEFAULT_INIT_FILE_NAME: &str = "openvm_init.rs"; const DEFAULT_U8_BLOCK_SIZE: usize = 4; const DEFAULT_NATIVE_BLOCK_SIZE: usize = 1; +/// The constant block size used for memory accesses when access adapters are disabled. +/// All memory accesses for address spaces 1-3 must use this block size. +/// This is also the block size used by the Boundary AIR for memory bus interactions. +pub const CONST_BLOCK_SIZE: usize = 4; + /// Trait for generating a init.rs file that contains a call to moduli_init!, /// complex_init!, sw_init! with the supported moduli and curves. /// Should be implemented by all VM config structs. @@ -183,6 +188,11 @@ pub struct MemoryConfig { pub decomp: usize, /// Maximum N AccessAdapter AIR to support. pub max_access_adapter_n: usize, + /// Whether access adapters are enabled. When disabled, all memory accesses must be of the + /// standard block size (e.g., 4 for address spaces 1-3). This removes the need for access + /// adapter AIRs and simplifies the memory system. + #[new(value = "true")] + pub access_adapters_enabled: bool, } impl Default for MemoryConfig { @@ -194,7 +204,15 @@ impl Default for MemoryConfig { addr_spaces[RV32_MEMORY_AS as usize].num_cells = MAX_CELLS; addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = DEFAULT_MAX_NUM_PUBLIC_VALUES; addr_spaces[NATIVE_AS as usize].num_cells = MAX_CELLS; - Self::new(3, addr_spaces, POINTER_MAX_BITS, 29, 17, 32) + Self { + addr_space_height: 3, + addr_spaces, + pointer_max_bits: POINTER_MAX_BITS, + timestamp_max_bits: 29, + decomp: 17, + max_access_adapter_n: 32, + access_adapters_enabled: true, + } } } @@ -245,6 +263,36 @@ impl MemoryConfig { .map(|addr_sp| log2_strict_usize(addr_sp.min_block_size) as u8) .collect() } + + /// Returns true if the Native address space (AS 4) is used. + /// Native AS is considered "used" if it has any allocated cells. + pub fn is_native_as_used(&self) -> bool { + self.addr_spaces + .get(NATIVE_AS as usize) + .is_some_and(|config| config.num_cells > 0) + } + + /// Disables access adapters. When disabled, all memory accesses for address spaces 1-3 + /// must use the constant block size (4). Access adapters will only be used for + /// address space 4 (Native) if it is enabled. + pub fn without_access_adapters(mut self) -> Self { + self.access_adapters_enabled = false; + self + } + + /// Enables access adapters. This is the default behavior. + pub fn with_access_adapters(mut self) -> Self { + self.access_adapters_enabled = true; + self + } + + /// Automatically sets `access_adapters_enabled` based on whether Native AS is used. + /// If Native AS is not used, access adapters are disabled since all other address spaces + /// use a fixed block size of 4. + pub fn with_auto_access_adapters(mut self) -> Self { + self.access_adapters_enabled = self.is_native_as_used(); + self + } } /// System-level configuration for the virtual machine. Contains all configuration parameters that @@ -375,6 +423,7 @@ impl SystemConfig { + num_memory_airs( self.continuation_enabled, self.memory_config.max_access_adapter_n, + self.memory_config.access_adapters_enabled, ) } @@ -384,6 +433,33 @@ impl SystemConfig { false => 1, } } + + /// Disables access adapters. When disabled, all memory accesses for address spaces 1-3 + /// must use the constant block size (4). This simplifies the memory system by removing + /// access adapter AIRs. + pub fn without_access_adapters(mut self) -> Self { + self.memory_config.access_adapters_enabled = false; + self + } + + /// Enables access adapters. This is the default behavior. + pub fn with_access_adapters(mut self) -> Self { + self.memory_config.access_adapters_enabled = true; + self + } + + /// Automatically sets `access_adapters_enabled` based on whether Native AS is used. + /// If Native AS is not used, access adapters are disabled since all other address spaces + /// use a fixed block size of 4. + pub fn with_auto_access_adapters(mut self) -> Self { + self.memory_config = self.memory_config.with_auto_access_adapters(); + self + } + + /// Returns true if access adapters are enabled. + pub fn access_adapters_enabled(&self) -> bool { + self.memory_config.access_adapters_enabled + } } impl Default for SystemConfig { diff --git a/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs b/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs index 3429177d11..d75dc2c46b 100644 --- a/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs +++ b/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs @@ -105,6 +105,7 @@ pub struct MemoryCtx { pub boundary_idx: usize, pub merkle_tree_index: Option, pub adapter_offset: usize, + access_adapters_enabled: bool, continuations_enabled: bool, chunk: u32, chunk_bits: u32, @@ -128,6 +129,7 @@ impl MemoryCtx { boundary_idx: config.memory_boundary_air_id(), merkle_tree_index: config.memory_merkle_air_id(), adapter_offset: config.access_adapter_air_id_offset(), + access_adapters_enabled: config.memory_config.access_adapters_enabled, chunk, chunk_bits, memory_dimensions, @@ -210,6 +212,11 @@ impl MemoryCtx { size_bits: u32, num: u32, ) { + // Skip if access adapters are disabled + if !self.access_adapters_enabled { + return; + } + debug_assert!((address_space as usize) < self.min_block_size_bits.len()); // SAFETY: address_space passed is usually a hardcoded constant or derived from an diff --git a/crates/vm/src/arch/execution_mode/metered_cost.rs b/crates/vm/src/arch/execution_mode/metered_cost.rs index 925bd25af2..c92965ad3f 100644 --- a/crates/vm/src/arch/execution_mode/metered_cost.rs +++ b/crates/vm/src/arch/execution_mode/metered_cost.rs @@ -18,6 +18,7 @@ pub const DEFAULT_MAX_COST: u64 = DEFAULT_MAX_SEGMENTS * DEFAULT_SEGMENT_MAX_CEL pub struct AccessAdapterCtx { min_block_size_bits: Vec, idx_offset: usize, + enabled: bool, } impl AccessAdapterCtx { @@ -25,6 +26,7 @@ impl AccessAdapterCtx { Self { min_block_size_bits: config.memory_config.min_block_size_bits(), idx_offset: config.access_adapter_air_id_offset(), + enabled: config.memory_config.access_adapters_enabled, } } @@ -36,6 +38,11 @@ impl AccessAdapterCtx { size_bits: u32, widths: &[usize], ) { + // Skip if access adapters are disabled + if !self.enabled { + return; + } + debug_assert!((address_space as usize) < self.min_block_size_bits.len()); // SAFETY: address_space passed is usually a hardcoded constant or derived from an diff --git a/crates/vm/src/system/memory/adapter/mod.rs b/crates/vm/src/system/memory/adapter/mod.rs index 8b0797dcf6..a9c89fc2ea 100644 --- a/crates/vm/src/system/memory/adapter/mod.rs +++ b/crates/vm/src/system/memory/adapter/mod.rs @@ -58,21 +58,26 @@ impl AccessAdapterInventory { memory_bus: MemoryBus, memory_config: MemoryConfig, ) -> Self { - let rc = range_checker; - let mb = memory_bus; - let tmb = memory_config.timestamp_max_bits; - let maan = memory_config.max_access_adapter_n; - assert!(matches!(maan, 2 | 4 | 8 | 16 | 32)); - let chips: Vec<_> = [ - Self::create_access_adapter_chip::<2>(rc.clone(), mb, tmb, maan), - Self::create_access_adapter_chip::<4>(rc.clone(), mb, tmb, maan), - Self::create_access_adapter_chip::<8>(rc.clone(), mb, tmb, maan), - Self::create_access_adapter_chip::<16>(rc.clone(), mb, tmb, maan), - Self::create_access_adapter_chip::<32>(rc.clone(), mb, tmb, maan), - ] - .into_iter() - .flatten() - .collect(); + // Only create adapter chips if access adapters are enabled + let chips: Vec<_> = if memory_config.access_adapters_enabled { + let rc = range_checker; + let mb = memory_bus; + let tmb = memory_config.timestamp_max_bits; + let maan = memory_config.max_access_adapter_n; + assert!(matches!(maan, 2 | 4 | 8 | 16 | 32)); + [ + Self::create_access_adapter_chip::<2>(rc.clone(), mb, tmb, maan), + Self::create_access_adapter_chip::<4>(rc.clone(), mb, tmb, maan), + Self::create_access_adapter_chip::<8>(rc.clone(), mb, tmb, maan), + Self::create_access_adapter_chip::<16>(rc.clone(), mb, tmb, maan), + Self::create_access_adapter_chip::<32>(rc.clone(), mb, tmb, maan), + ] + .into_iter() + .flatten() + .collect() + } else { + Vec::new() + }; Self { memory_config, chips, diff --git a/crates/vm/src/system/memory/mod.rs b/crates/vm/src/system/memory/mod.rs index 411e7a5473..8c3f48c7f0 100644 --- a/crates/vm/src/system/memory/mod.rs +++ b/crates/vm/src/system/memory/mod.rs @@ -118,20 +118,24 @@ impl MemoryAirInventory { ); MemoryInterfaceAirs::Volatile { boundary } }; - // Memory access adapters - let lt_air = IsLtSubAir::new(range_bus, mem_config.timestamp_max_bits); - let maan = mem_config.max_access_adapter_n; - assert!(matches!(maan, 2 | 4 | 8 | 16 | 32)); - let access_adapters: Vec> = [ - Arc::new(AccessAdapterAir::<2> { memory_bus, lt_air }) as AirRef, - Arc::new(AccessAdapterAir::<4> { memory_bus, lt_air }) as AirRef, - Arc::new(AccessAdapterAir::<8> { memory_bus, lt_air }) as AirRef, - Arc::new(AccessAdapterAir::<16> { memory_bus, lt_air }) as AirRef, - Arc::new(AccessAdapterAir::<32> { memory_bus, lt_air }) as AirRef, - ] - .into_iter() - .take(log2_strict_usize(maan)) - .collect(); + // Memory access adapters - only create if enabled + let access_adapters: Vec> = if mem_config.access_adapters_enabled { + let lt_air = IsLtSubAir::new(range_bus, mem_config.timestamp_max_bits); + let maan = mem_config.max_access_adapter_n; + assert!(matches!(maan, 2 | 4 | 8 | 16 | 32)); + [ + Arc::new(AccessAdapterAir::<2> { memory_bus, lt_air }) as AirRef, + Arc::new(AccessAdapterAir::<4> { memory_bus, lt_air }) as AirRef, + Arc::new(AccessAdapterAir::<8> { memory_bus, lt_air }) as AirRef, + Arc::new(AccessAdapterAir::<16> { memory_bus, lt_air }) as AirRef, + Arc::new(AccessAdapterAir::<32> { memory_bus, lt_air }) as AirRef, + ] + .into_iter() + .take(log2_strict_usize(maan)) + .collect() + } else { + Vec::new() + }; Self { bridge, @@ -159,7 +163,16 @@ impl MemoryAirInventory { /// This is O(1) and returns the length of /// [`MemoryAirInventory::into_airs`]. -pub fn num_memory_airs(is_persistent: bool, max_access_adapter_n: usize) -> usize { - // boundary + { merkle if is_persistent } + access_adapters - 1 + usize::from(is_persistent) + log2_strict_usize(max_access_adapter_n) +pub fn num_memory_airs( + is_persistent: bool, + max_access_adapter_n: usize, + access_adapters_enabled: bool, +) -> usize { + // boundary + { merkle if is_persistent } + access_adapters (if enabled) + let num_adapters = if access_adapters_enabled { + log2_strict_usize(max_access_adapter_n) + } else { + 0 + }; + 1 + usize::from(is_persistent) + num_adapters } From 10a0fc4370d9c890eb45890b42d80393e01e4a37 Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 11 Dec 2025 20:03:46 +0000 Subject: [PATCH 02/19] finalize memory --- crates/vm/src/system/memory/online.rs | 108 +++++++++++++++++++------- 1 file changed, 81 insertions(+), 27 deletions(-) diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index 6a16e0d12b..3c5ac45f45 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -13,7 +13,7 @@ use tracing::instrument; use crate::{ arch::{ AddressSpaceHostConfig, AddressSpaceHostLayout, DenseRecordArena, MemoryConfig, - RecordArena, MAX_CELL_BYTE_SIZE, + RecordArena, CONST_BLOCK_SIZE, MAX_CELL_BYTE_SIZE, }, system::{ memory::{ @@ -941,10 +941,10 @@ impl TracingMemory { match is_persistent { false => TouchedMemory::Volatile( - self.touched_blocks_to_equipartition::(touched_blocks), + self.touched_blocks_to_equipartition::(touched_blocks), ), true => TouchedMemory::Persistent( - self.touched_blocks_to_equipartition::(touched_blocks), + self.touched_blocks_to_equipartition::(touched_blocks), ), } } @@ -974,29 +974,37 @@ impl TracingMemory { /// Returns the equipartition of the touched blocks. /// Modifies records and adds new to account for the initial/final segments. - fn touched_blocks_to_equipartition( + fn touched_blocks_to_equipartition< + F: Field, + const PARTITION_SIZE: usize, + const OUTPUT_SIZE: usize, + >( &mut self, touched_blocks: Vec<((u32, u32), AccessMetadata)>, - ) -> TimestampedEquipartition { + ) -> TimestampedEquipartition { + assert!( + OUTPUT_SIZE % PARTITION_SIZE == 0, + "Output size must be a multiple of the partition size" + ); // [perf] We can `.with_capacity()` if we keep track of the number of segments we initialize - let mut final_memory = Vec::new(); + let mut partitioned_memory = Vec::new(); debug_assert!(touched_blocks.is_sorted_by_key(|(addr, _)| addr)); - self.handle_touched_blocks::(&mut final_memory, touched_blocks); + self.handle_touched_blocks::(&mut partitioned_memory, touched_blocks); - debug_assert!(final_memory.is_sorted_by_key(|(key, _)| *key)); - final_memory + debug_assert!(partitioned_memory.is_sorted_by_key(|(key, _)| *key)); + Self::rechunk_final_memory::(partitioned_memory) } - fn handle_touched_blocks( + fn handle_touched_blocks( &mut self, - final_memory: &mut Vec<((u32, u32), TimestampedValues)>, + final_memory: &mut Vec<((u32, u32), TimestampedValues)>, touched_blocks: Vec<((u32, u32), AccessMetadata)>, ) { - let mut current_values = vec![0u8; MAX_CELL_BYTE_SIZE * CHUNK]; + let mut current_values = vec![0u8; MAX_CELL_BYTE_SIZE * PARTITION_SIZE]; let mut current_cnt = 0; let mut current_address = MemoryAddress::new(0, 0); - let mut current_timestamps = vec![0; CHUNK]; + let mut current_timestamps = vec![0; PARTITION_SIZE]; for ((addr_space, ptr), access_metadata) in touched_blocks { // SAFETY: addr_space of touched blocks are all in bounds let addr_space_config = @@ -1009,16 +1017,16 @@ impl TracingMemory { current_cnt == 0 || (current_address.address_space == addr_space && current_address.pointer + current_cnt as u32 == ptr), - "The union of all touched blocks must consist of blocks with sizes divisible by `CHUNK`" + "The union of all touched blocks must consist of blocks with sizes divisible by the partition size" ); debug_assert!(block_size >= min_block_size as u8); debug_assert!(ptr % min_block_size as u32 == 0); if current_cnt == 0 { assert_eq!( - ptr & (CHUNK as u32 - 1), + ptr & (PARTITION_SIZE as u32 - 1), 0, - "The union of all touched blocks must consist of `CHUNK`-aligned blocks" + "The union of all touched blocks must consist of partition-aligned blocks" ); current_address = MemoryAddress::new(addr_space, ptr); } @@ -1033,7 +1041,7 @@ impl TracingMemory { type_size: cell_size as u32, }); } - if min_block_size > CHUNK { + if min_block_size > PARTITION_SIZE { assert_eq!(current_cnt, 0); for i in (0..block_size as u32).step_by(min_block_size) { self.add_split_record(AccessRecordHeader { @@ -1041,7 +1049,7 @@ impl TracingMemory { address_space: addr_space, pointer: ptr + i, block_size: min_block_size as u32, - lowest_block_size: CHUNK as u32, + lowest_block_size: PARTITION_SIZE as u32, type_size: cell_size as u32, }); } @@ -1053,14 +1061,14 @@ impl TracingMemory { block_size as usize * cell_size, ) }; - for i in (0..block_size as u32).step_by(CHUNK) { + for i in (0..block_size as u32).step_by(PARTITION_SIZE) { final_memory.push(( (addr_space, ptr + i), TimestampedValues { timestamp, values: from_fn(|j| { let byte_idx = (i as usize + j) * cell_size; - // SAFETY: block_size is multiple of CHUNK and we are reading chunks + // SAFETY: block_size is multiple of PARTITION_SIZE and we are reading chunks // of cells within bounds unsafe { addr_space_config @@ -1084,15 +1092,15 @@ impl TracingMemory { current_values[current_cnt * cell_size..current_cnt * cell_size + cell_size] .copy_from_slice(cell_data); if current_cnt & (min_block_size - 1) == 0 { - // SAFETY: current_cnt / min_block_size < CHUNK / min_block_size <= CHUNK + // SAFETY: current_cnt / min_block_size < PARTITION_SIZE / min_block_size <= PARTITION_SIZE unsafe { *current_timestamps.get_unchecked_mut(current_cnt / min_block_size) = timestamp; } } current_cnt += 1; - if current_cnt == CHUNK { - let timestamp = *current_timestamps[..CHUNK / min_block_size] + if current_cnt == PARTITION_SIZE { + let timestamp = *current_timestamps[..PARTITION_SIZE / min_block_size] .iter() .max() .unwrap(); @@ -1101,12 +1109,12 @@ impl TracingMemory { timestamp_and_mask: timestamp, address_space: addr_space, pointer: current_address.pointer, - block_size: CHUNK as u32, + block_size: PARTITION_SIZE as u32, lowest_block_size: min_block_size as u32, type_size: cell_size as u32, }, - ¤t_values[..CHUNK * cell_size], - ¤t_timestamps[..CHUNK / min_block_size], + ¤t_values[..PARTITION_SIZE * cell_size], + ¤t_timestamps[..PARTITION_SIZE / min_block_size], ); final_memory.push(( (current_address.address_space, current_address.pointer), @@ -1126,7 +1134,53 @@ impl TracingMemory { } } } - assert_eq!(current_cnt, 0, "The union of all touched blocks must consist of blocks with sizes divisible by `CHUNK`"); + assert_eq!( + current_cnt, 0, + "The union of all touched blocks must consist of blocks with sizes divisible by the partition size" + ); + } + + fn rechunk_final_memory( + partitioned_memory: Vec<((u32, u32), TimestampedValues)>, + ) -> TimestampedEquipartition { + debug_assert!(OUTPUT_SIZE % PARTITION_SIZE == 0); + let merge_factor = OUTPUT_SIZE / PARTITION_SIZE; + let mut final_memory = + Vec::with_capacity(partitioned_memory.len().saturating_div(merge_factor)); + let mut idx = 0; + while idx < partitioned_memory.len() { + debug_assert!(idx + merge_factor <= partitioned_memory.len()); + + let group = &partitioned_memory[idx..idx + merge_factor]; + let ((addr_space, base_ptr), _) = group[0]; + debug_assert_eq!(base_ptr % OUTPUT_SIZE as u32, 0); + + for (j, ((curr_addr_space, ptr), _)) in group.iter().enumerate() { + debug_assert_eq!(*curr_addr_space, addr_space); + debug_assert_eq!(*ptr, base_ptr + (j * PARTITION_SIZE) as u32); + } + + let timestamp = group + .iter() + .map(|(_, ts_values)| ts_values.timestamp) + .max() + .expect("Group is non-empty"); + let values = from_fn(|i| { + let group_idx = i / PARTITION_SIZE; + let within_group_idx = i % PARTITION_SIZE; + group[group_idx].1.values[within_group_idx] + }); + + final_memory.push(( + (addr_space, base_ptr), + TimestampedValues { timestamp, values }, + )); + + idx += merge_factor; + } + + debug_assert!(final_memory.is_sorted_by_key(|(key, _)| *key)); + final_memory } pub fn address_space_alignment(&self) -> Vec { From 9f5d2d6e09b98ae027d7246b0cae5c4ba72ecebe Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 19:08:21 +0000 Subject: [PATCH 03/19] wip --- crates/vm/src/arch/config.rs | 2 +- crates/vm/src/arch/state.rs | 15 ++++++++- crates/vm/src/arch/testing/cpu.rs | 11 +++++-- crates/vm/src/system/memory/adapter/mod.rs | 8 +++++ crates/vm/src/system/memory/online.rs | 36 +++++++++++++++++++--- crates/vm/src/system/memory/persistent.rs | 29 ++++++++++------- extensions/rv32im/tests/src/lib.rs | 4 +-- 7 files changed, 84 insertions(+), 21 deletions(-) diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 17f7d228eb..50467de631 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -429,7 +429,7 @@ impl SystemConfig { pub fn initial_block_size(&self) -> usize { match self.continuation_enabled { - true => CHUNK, + true => CONST_BLOCK_SIZE, false => 1, } } diff --git a/crates/vm/src/arch/state.rs b/crates/vm/src/arch/state.rs index 6e79677541..1da2a4d392 100644 --- a/crates/vm/src/arch/state.rs +++ b/crates/vm/src/arch/state.rs @@ -1,4 +1,5 @@ use std::{ + backtrace::Backtrace, fmt::Debug, ops::{Deref, DerefMut}, }; @@ -13,7 +14,7 @@ use super::{create_memory_image, ExecutionError, Streams}; #[cfg(feature = "metrics")] use crate::metrics::VmMetrics; use crate::{ - arch::{execution_mode::ExecutionCtxTrait, SystemConfig, VmStateMut}, + arch::{execution_mode::ExecutionCtxTrait, SystemConfig, VmStateMut, CONST_BLOCK_SIZE}, system::memory::online::GuestMemory, }; @@ -187,6 +188,12 @@ where addr_space: u32, ptr: u32, ) -> [T; BLOCK_SIZE] { + if BLOCK_SIZE != CONST_BLOCK_SIZE { + println!( + "vm_read: addr_space = {}, ptr = {}, BLOCK_SIZE = {}", + addr_space, ptr, BLOCK_SIZE + ); + } self.ctx .on_memory_operation(addr_space, ptr, BLOCK_SIZE as u32); self.host_read(addr_space, ptr) @@ -200,6 +207,12 @@ where ptr: u32, data: &[T; BLOCK_SIZE], ) { + if BLOCK_SIZE != CONST_BLOCK_SIZE { + println!( + "vm_read: addr_space = {}, ptr = {}, BLOCK_SIZE = {}", + addr_space, ptr, BLOCK_SIZE + ); + } self.ctx .on_memory_operation(addr_space, ptr, BLOCK_SIZE as u32); self.host_write(addr_space, ptr, data) diff --git a/crates/vm/src/arch/testing/cpu.rs b/crates/vm/src/arch/testing/cpu.rs index 105962bc11..4c574dda73 100644 --- a/crates/vm/src/arch/testing/cpu.rs +++ b/crates/vm/src/arch/testing/cpu.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use itertools::zip_eq; +use openvm_circuit::arch::CONST_BLOCK_SIZE; use openvm_circuit_primitives::var_range::{ SharedVariableRangeCheckerChip, VariableRangeCheckerBus, VariableRangeCheckerChip, }; @@ -332,7 +333,7 @@ impl VmChipTestBuilder { fn range_checker_and_memory( mem_config: &MemoryConfig, - init_block_size: usize, + init_block_size: usize, // modify this to CONST_BLOCK_SIZE ) -> (SharedVariableRangeCheckerChip, TracingMemory) { let range_checker = Arc::new(VariableRangeCheckerChip::new(VariableRangeCheckerBus::new( RANGE_CHECKER_BUS, @@ -347,7 +348,12 @@ impl VmChipTestBuilder { pub fn persistent(mem_config: MemoryConfig) -> Self { setup_tracing_with_log_level(Level::INFO); - let (range_checker, memory) = Self::range_checker_and_memory(&mem_config, CHUNK); + /// ERRRMMM WHAT THE SIGMA not testing here + println!( + "PERSISTENT MEMORY TESTING, CONST_BLOCK_SIZE = {}", + CONST_BLOCK_SIZE + ); + let (range_checker, memory) = Self::range_checker_and_memory(&mem_config, CONST_BLOCK_SIZE); let hasher_chip = Arc::new(Poseidon2PeripheryChip::new( vm_poseidon2_config(), POSEIDON2_DIRECT_BUS, @@ -470,6 +476,7 @@ where let mut memory_controller = memory_tester.controller; let is_persistent = memory_controller.continuation_enabled(); let mut memory = memory_tester.memory; + // here? pass in initial memory for chunking let touched_memory = memory.finalize::>(is_persistent); // Balance memory boundaries let range_checker = memory_controller.range_checker.clone(); diff --git a/crates/vm/src/system/memory/adapter/mod.rs b/crates/vm/src/system/memory/adapter/mod.rs index a9c89fc2ea..2ae0fc28eb 100644 --- a/crates/vm/src/system/memory/adapter/mod.rs +++ b/crates/vm/src/system/memory/adapter/mod.rs @@ -1,4 +1,5 @@ use std::{ + backtrace::Backtrace, borrow::{Borrow, BorrowMut}, marker::PhantomData, ptr::copy_nonoverlapping, @@ -132,6 +133,13 @@ impl AccessAdapterInventory { while ptr < bytes.len() { let bytes_slice = &bytes[ptr..]; let header: &AccessRecordHeader = bytes_slice.borrow(); + + if header.block_size == 8 { + println!("Found size 8 access:"); + println!(" Address space: {}", header.address_space); + println!(" Pointer: {}", header.pointer); + println!(" Timestamp: {}", header.timestamp_and_mask); + } // SAFETY: // - bytes[ptr..] is a valid starting pointer to a previously allocated record // - The record contains self-describing layout information diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index 3c5ac45f45..308bd58a06 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -1,4 +1,4 @@ -use std::{array::from_fn, fmt::Debug, num::NonZero}; +use std::{array::from_fn, backtrace::Backtrace, fmt::Debug, num::NonZero}; use getset::Getters; use itertools::zip_eq; @@ -440,7 +440,7 @@ pub struct TracingMemory { initial_block_size: usize, /// The underlying data memory, with memory cells typed by address space: see [AddressMap]. #[getset(get = "pub")] - pub data: GuestMemory, + pub data: GuestMemory, // is this "initial memory" /// Maps addr_space to (ptr / min_block_size[addr_space] -> AccessMetadata) for latest access /// metadata. Uses paged storage for memory efficiency. AccessMetadata stores offset_to_start /// (in ALIGN units), block_size, and timestamp (latter two only valid at offset_to_start == @@ -577,6 +577,16 @@ impl TracingMemory { } pub(crate) fn add_split_record(&mut self, header: AccessRecordHeader) { + if header.block_size == 8 { + println!("-----SPLIT-----"); + println!("Adding split record for size 8:"); + println!(" Address space: {}", header.address_space); + println!(" Pointer: {}", header.pointer); + println!(" Timestamp: {}", header.timestamp_and_mask); + + let bt = Backtrace::capture(); + println!("{bt}"); + } if header.block_size == header.lowest_block_size { return; } @@ -602,6 +612,8 @@ impl TracingMemory { // we don't mind garbage values in prev_* } + //appears that we are initially still splitting, and merging memory + // is this from merkle tree? /// `data_slice` is the underlying data of the record in raw host memory format. pub(crate) fn add_merge_record( &mut self, @@ -609,6 +621,16 @@ impl TracingMemory { data_slice: &[u8], prev_ts: &[u32], ) { + if header.block_size == 8 { + println!("-----MERGE-----"); + println!("Adding merge record for size 8:"); + println!(" Address space: {}", header.address_space); + println!(" Pointer: {}", header.pointer); + println!(" Timestamp: {}", header.timestamp_and_mask); + + let bt = Backtrace::capture(); + println!("{bt}"); + } if header.block_size == header.lowest_block_size { return; } @@ -695,6 +717,10 @@ impl TracingMemory { AccessMetadata::new(timestamp, MIN_BLOCK_SIZE as u8, 0), ); } + println!( + "BLOCK SIZE: {}, MIN_BLOCK_SIZE: {}", + block_size, MIN_BLOCK_SIZE + ); self.add_split_record(AccessRecordHeader { timestamp_and_mask: timestamp, address_space: address_space as u32, @@ -935,6 +961,7 @@ impl TracingMemory { } /// Finalize the boundary and merkle chips. + /// pass in initial memory,for rechunking #[instrument(name = "memory_finalize", skip_all)] pub fn finalize(&mut self, is_persistent: bool) -> TouchedMemory { let touched_blocks = self.touched_blocks(); @@ -1148,9 +1175,10 @@ impl TracingMemory { let mut final_memory = Vec::with_capacity(partitioned_memory.len().saturating_div(merge_factor)); let mut idx = 0; - while idx < partitioned_memory.len() { - debug_assert!(idx + merge_factor <= partitioned_memory.len()); + //currently naively merging, but we need to consider incomplete blocks of PARTITION_SIZE, and make it into CHUNK + // with an initial PARTITION, keep on adding ind until it matches OUTPUT_SIZE; look at it mod OUTPUT_SIZE + while idx < partitioned_memory.len() { let group = &partitioned_memory[idx..idx + merge_factor]; let ((addr_space, base_ptr), _) = group[0]; debug_assert_eq!(base_ptr % OUTPUT_SIZE as u32, 0); diff --git a/crates/vm/src/system/memory/persistent.rs b/crates/vm/src/system/memory/persistent.rs index eeb22cbfd6..b5e8f5c1b3 100644 --- a/crates/vm/src/system/memory/persistent.rs +++ b/crates/vm/src/system/memory/persistent.rs @@ -22,7 +22,7 @@ use tracing::instrument; use super::{merkle::SerialReceiver, online::INITIAL_TIMESTAMP, TimestampedValues}; use crate::{ - arch::{hasher::Hasher, ADDR_SPACE_OFFSET}, + arch::{hasher::Hasher, ADDR_SPACE_OFFSET, CONST_BLOCK_SIZE}, system::memory::{ dimensions::MemoryDimensions, offline_checker::MemoryBus, MemoryAddress, MemoryImage, TimestampedEquipartition, @@ -109,16 +109,23 @@ impl Air for PersistentBoundaryA local.expand_direction * local.expand_direction, ); - self.memory_bus - .send( - MemoryAddress::new( - local.address_space, - local.leaf_label * AB::F::from_canonical_usize(CHUNK), - ), - local.values.to_vec(), - local.timestamp, - ) - .eval(builder, local.expand_direction); + debug_assert_eq!(CHUNK % CONST_BLOCK_SIZE, 0); + let chunk_size_f = AB::F::from_canonical_usize(CHUNK); + for block_idx in 0..(CHUNK / CONST_BLOCK_SIZE) { + let offset = AB::F::from_canonical_usize(block_idx * CONST_BLOCK_SIZE); + // Split the 1xCHUNK leaf into CONST_BLOCK_SIZE-sized bus messages. + self.memory_bus + .send( + MemoryAddress::new( + local.address_space, + local.leaf_label * chunk_size_f + offset, + ), + local.values[block_idx * CONST_BLOCK_SIZE..(block_idx + 1) * CONST_BLOCK_SIZE] + .to_vec(), + local.timestamp, + ) + .eval(builder, local.expand_direction); + } } } diff --git a/extensions/rv32im/tests/src/lib.rs b/extensions/rv32im/tests/src/lib.rs index c4302ae808..f4f0efd0d3 100644 --- a/extensions/rv32im/tests/src/lib.rs +++ b/extensions/rv32im/tests/src/lib.rs @@ -93,9 +93,9 @@ mod tests { Ok(()) } - #[test_case("fibonacci", 1)] + // #[test_case("fibonacci", 1)] #[test_case("collatz", 1)] - fn test_rv32im(example_name: &str, min_segments: usize) -> Result<()> { + fn test_rv32m(example_name: &str, min_segments: usize) -> Result<()> { let config = test_rv32im_config(); let elf = build_example_program_at_path(get_programs_dir!(), example_name, &config)?; let exe = VmExe::from_elf( From a2d8b0108c12aa443cd87bb6c19bbb04f0b3c353 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 21:38:13 +0000 Subject: [PATCH 04/19] rv32im --- crates/vm/src/arch/config.rs | 4 +- crates/vm/src/system/memory/controller/mod.rs | 32 ++++-- crates/vm/src/system/memory/online.rs | 55 ++--------- crates/vm/src/system/memory/persistent.rs | 97 ++++++++++++++----- crates/vm/src/system/mod.rs | 5 +- 5 files changed, 111 insertions(+), 82 deletions(-) diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 50467de631..02e790a932 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -26,9 +26,7 @@ use crate::{ Arena, ChipInventoryError, ExecutorInventory, ExecutorInventoryError, }, system::{ - memory::{ - merkle::public_values::PUBLIC_VALUES_AS, num_memory_airs, CHUNK, POINTER_MAX_BITS, - }, + memory::{merkle::public_values::PUBLIC_VALUES_AS, num_memory_airs, POINTER_MAX_BITS}, SystemChipComplex, }, }; diff --git a/crates/vm/src/system/memory/controller/mod.rs b/crates/vm/src/system/memory/controller/mod.rs index aabe4df08d..bd770d163e 100644 --- a/crates/vm/src/system/memory/controller/mod.rs +++ b/crates/vm/src/system/memory/controller/mod.rs @@ -14,7 +14,6 @@ use openvm_stark_backend::{ interaction::PermutationCheckBus, p3_commit::PolynomialSpace, p3_field::{Field, PrimeField32}, - p3_maybe_rayon::prelude::{IntoParallelIterator, ParallelIterator}, p3_util::{log2_ceil_usize, log2_strict_usize}, prover::{cpu::CpuBackend, types::AirProvingContext}, Chip, @@ -24,7 +23,7 @@ use serde::{Deserialize, Serialize}; use self::interface::MemoryInterface; use super::{volatile::VolatileBoundaryChip, AddressMap}; use crate::{ - arch::{DenseRecordArena, MemoryConfig, ADDR_SPACE_OFFSET}, + arch::{DenseRecordArena, MemoryConfig, ADDR_SPACE_OFFSET, CONST_BLOCK_SIZE}, system::{ memory::{ adapter::AccessAdapterInventory, @@ -290,11 +289,32 @@ impl MemoryController { TouchedMemory::Persistent(final_memory), ) => { let hasher = self.hasher_chip.as_ref().unwrap(); + // boundary_chip.finalize takes CONST_BLOCK_SIZE granularity and rechunks internally boundary_chip.finalize(initial_memory, &final_memory, hasher.as_ref()); - let final_memory_values = final_memory - .into_par_iter() - .map(|(key, value)| (key, value.values)) - .collect(); + + // Rechunk CONST_BLOCK_SIZE blocks into CHUNK-sized blocks for merkle_chip + // Note: Equipartition key is (addr_space, ptr) where ptr is the starting pointer + let final_memory_values: Equipartition = { + use std::collections::BTreeMap; + let mut chunk_map: BTreeMap<(u32, u32), [F; CHUNK]> = BTreeMap::new(); + for ((addr_space, ptr), ts_values) in final_memory.into_iter() { + // Align to CHUNK boundary to get the chunk's starting pointer + let chunk_ptr = (ptr / CHUNK as u32) * CHUNK as u32; + let block_idx_in_chunk = + ((ptr % CHUNK as u32) / CONST_BLOCK_SIZE as u32) as usize; + let entry = chunk_map.entry((addr_space, chunk_ptr)).or_insert_with(|| { + // Initialize with values from initial memory + std::array::from_fn(|i| unsafe { + initial_memory.get_f::(addr_space, chunk_ptr + i as u32) + }) + }); + // Copy values for this block + for (i, val) in ts_values.values.into_iter().enumerate() { + entry[block_idx_in_chunk * CONST_BLOCK_SIZE + i] = val; + } + } + chunk_map + }; merkle_chip.finalize(initial_memory, &final_memory_values, hasher.as_ref()); } _ => panic!("TouchedMemory incorrect type"), diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index 308bd58a06..021863c54b 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -18,7 +18,7 @@ use crate::{ system::{ memory::{ adapter::records::{AccessLayout, AccessRecordHeader, MERGE_AND_NOT_SPLIT_FLAG}, - MemoryAddress, TimestampedEquipartition, TimestampedValues, CHUNK, + MemoryAddress, TimestampedEquipartition, TimestampedValues, }, TouchedMemory, }, @@ -971,7 +971,9 @@ impl TracingMemory { self.touched_blocks_to_equipartition::(touched_blocks), ), true => TouchedMemory::Persistent( - self.touched_blocks_to_equipartition::(touched_blocks), + self.touched_blocks_to_equipartition::( + touched_blocks, + ), ), } } @@ -1017,10 +1019,11 @@ impl TracingMemory { let mut partitioned_memory = Vec::new(); debug_assert!(touched_blocks.is_sorted_by_key(|(addr, _)| addr)); - self.handle_touched_blocks::(&mut partitioned_memory, touched_blocks); + self.handle_touched_blocks::(&mut partitioned_memory, touched_blocks); debug_assert!(partitioned_memory.is_sorted_by_key(|(key, _)| *key)); - Self::rechunk_final_memory::(partitioned_memory) + partitioned_memory + // self.rechunk_final_memory::(partitioned_memory) } fn handle_touched_blocks( @@ -1167,50 +1170,6 @@ impl TracingMemory { ); } - fn rechunk_final_memory( - partitioned_memory: Vec<((u32, u32), TimestampedValues)>, - ) -> TimestampedEquipartition { - debug_assert!(OUTPUT_SIZE % PARTITION_SIZE == 0); - let merge_factor = OUTPUT_SIZE / PARTITION_SIZE; - let mut final_memory = - Vec::with_capacity(partitioned_memory.len().saturating_div(merge_factor)); - let mut idx = 0; - //currently naively merging, but we need to consider incomplete blocks of PARTITION_SIZE, and make it into CHUNK - // with an initial PARTITION, keep on adding ind until it matches OUTPUT_SIZE; look at it mod OUTPUT_SIZE - - while idx < partitioned_memory.len() { - let group = &partitioned_memory[idx..idx + merge_factor]; - let ((addr_space, base_ptr), _) = group[0]; - debug_assert_eq!(base_ptr % OUTPUT_SIZE as u32, 0); - - for (j, ((curr_addr_space, ptr), _)) in group.iter().enumerate() { - debug_assert_eq!(*curr_addr_space, addr_space); - debug_assert_eq!(*ptr, base_ptr + (j * PARTITION_SIZE) as u32); - } - - let timestamp = group - .iter() - .map(|(_, ts_values)| ts_values.timestamp) - .max() - .expect("Group is non-empty"); - let values = from_fn(|i| { - let group_idx = i / PARTITION_SIZE; - let within_group_idx = i % PARTITION_SIZE; - group[group_idx].1.values[within_group_idx] - }); - - final_memory.push(( - (addr_space, base_ptr), - TimestampedValues { timestamp, values }, - )); - - idx += merge_factor; - } - - debug_assert!(final_memory.is_sorted_by_key(|(key, _)| *key)); - final_memory - } - pub fn address_space_alignment(&self) -> Vec { self.min_block_size .iter() diff --git a/crates/vm/src/system/memory/persistent.rs b/crates/vm/src/system/memory/persistent.rs index b5e8f5c1b3..f3f650fbe0 100644 --- a/crates/vm/src/system/memory/persistent.rs +++ b/crates/vm/src/system/memory/persistent.rs @@ -20,7 +20,7 @@ use openvm_stark_backend::{ use rustc_hash::FxHashSet; use tracing::instrument; -use super::{merkle::SerialReceiver, online::INITIAL_TIMESTAMP, TimestampedValues}; +use super::{merkle::SerialReceiver, online::INITIAL_TIMESTAMP}; use crate::{ arch::{hasher::Hasher, ADDR_SPACE_OFFSET, CONST_BLOCK_SIZE}, system::memory::{ @@ -29,6 +29,11 @@ use crate::{ }, }; +/// Number of CONST_BLOCK_SIZE blocks per CHUNK (e.g., 2 for 8/4). +/// Blocks are on the same row only for Merkle tree hashing (8 bytes at a time). +/// Memory bus interactions use per-block timestamps. +pub const BLOCKS_PER_CHUNK: usize = 2; + /// The values describe aligned chunk of memory of size `CHUNK`---the data together with the last /// accessed timestamp---in either the initial or final memory state. #[repr(C)] @@ -42,7 +47,9 @@ pub struct PersistentBoundaryCols { pub leaf_label: T, pub values: [T; CHUNK], pub hash: [T; CHUNK], - pub timestamp: T, + /// Per-block timestamps. Each CONST_BLOCK_SIZE block within the chunk has its own timestamp. + /// For untouched blocks, timestamp stays at 0 (balances: boundary sends at t=0 init, receives at t=0 final). + pub timestamps: [T; BLOCKS_PER_CHUNK], } /// Imposes the following constraints: @@ -81,12 +88,14 @@ impl Air for PersistentBoundaryA local.expand_direction * local.expand_direction * local.expand_direction, ); - // Constrain that an "initial" row has timestamp zero. + // Constrain that an "initial" row has all timestamp zero. // Since `direction` is constrained to be in {-1, 0, 1}, we can select `direction == 1` // with the constraint below. - builder - .when(local.expand_direction * (local.expand_direction + AB::F::ONE)) - .assert_zero(local.timestamp); + let mut when_initial = + builder.when(local.expand_direction * (local.expand_direction + AB::F::ONE)); + for i in 0..BLOCKS_PER_CHUNK { + when_initial.assert_zero(local.timestamps[i]); + } let mut expand_fields = vec![ // direction = 1 => is_final = 0 @@ -110,10 +119,12 @@ impl Air for PersistentBoundaryA ); debug_assert_eq!(CHUNK % CONST_BLOCK_SIZE, 0); + debug_assert_eq!(CHUNK / CONST_BLOCK_SIZE, BLOCKS_PER_CHUNK); let chunk_size_f = AB::F::from_canonical_usize(CHUNK); - for block_idx in 0..(CHUNK / CONST_BLOCK_SIZE) { + for block_idx in 0..BLOCKS_PER_CHUNK { let offset = AB::F::from_canonical_usize(block_idx * CONST_BLOCK_SIZE); // Split the 1xCHUNK leaf into CONST_BLOCK_SIZE-sized bus messages. + // Each block uses its own timestamp - untouched blocks stay at t=0. self.memory_bus .send( MemoryAddress::new( @@ -122,7 +133,7 @@ impl Air for PersistentBoundaryA ), local.values[block_idx * CONST_BLOCK_SIZE..(block_idx + 1) * CONST_BLOCK_SIZE] .to_vec(), - local.timestamp, + local.timestamps[block_idx], ) .eval(builder, local.expand_direction); } @@ -149,7 +160,8 @@ pub struct FinalTouchedLabel { final_values: [F; CHUNK], init_hash: [F; CHUNK], final_hash: [F; CHUNK], - final_timestamp: u32, + /// Per-block timestamps. Each CONST_BLOCK_SIZE block has its own timestamp. + final_timestamps: [u32; BLOCKS_PER_CHUNK], } impl Default for TouchedLabels { @@ -214,34 +226,68 @@ impl PersistentBoundaryChip { } } + /// Finalize the boundary chip with per-block timestamped memory. + /// + /// `final_memory` is at CONST_BLOCK_SIZE granularity (4 bytes per entry, single timestamp each). + /// This function rechunks into CHUNK-sized (8 bytes) groups with per-block timestamps. + /// Untouched blocks within a touched chunk get values from initial_memory and timestamp 0. #[instrument(name = "boundary_finalize", level = "debug", skip_all)] pub(crate) fn finalize( &mut self, initial_memory: &MemoryImage, - // Only touched stuff - final_memory: &TimestampedEquipartition, + // Touched stuff at CONST_BLOCK_SIZE granularity + final_memory: &TimestampedEquipartition, hasher: &H, ) where H: Hasher + Sync + for<'a> SerialReceiver<&'a [F]>, { - let final_touched_labels: Vec<_> = final_memory - .par_iter() - .map(|&((addr_space, ptr), ts_values)| { - let TimestampedValues { timestamp, values } = ts_values; + // Group CONST_BLOCK_SIZE blocks into CHUNK-sized groups + // Key: (addr_space, chunk_label), Value: per-block timestamps and values + use std::collections::BTreeMap; + let mut chunk_map: BTreeMap<(u32, u32), ([u32; BLOCKS_PER_CHUNK], [F; CHUNK])> = + BTreeMap::new(); + + for &((addr_space, ptr), ts_values) in final_memory.iter() { + let chunk_label = ptr / CHUNK as u32; + let block_idx_in_chunk = ((ptr % CHUNK as u32) / CONST_BLOCK_SIZE as u32) as usize; + + let entry = chunk_map + .entry((addr_space, chunk_label)) + .or_insert_with(|| { + // Initialize with values from initial memory and timestamps at 0 + let chunk_ptr = chunk_label * CHUNK as u32; + let init_values: [F; CHUNK] = array::from_fn(|i| unsafe { + initial_memory.get_f::(addr_space, chunk_ptr + i as u32) + }); + ([0u32; BLOCKS_PER_CHUNK], init_values) + }); + + // Set per-block timestamp + entry.0[block_idx_in_chunk] = ts_values.timestamp; + // Copy values for this block + for (i, &val) in ts_values.values.iter().enumerate() { + entry.1[block_idx_in_chunk * CONST_BLOCK_SIZE + i] = val; + } + } + + let final_touched_labels: Vec<_> = chunk_map + .into_par_iter() + .map(|((addr_space, chunk_label), (timestamps, final_values))| { + let chunk_ptr = chunk_label * CHUNK as u32; // SAFETY: addr_space from `final_memory` are all in bounds - let init_values = array::from_fn(|i| unsafe { - initial_memory.get_f::(addr_space, ptr + i as u32) + let init_values: [F; CHUNK] = array::from_fn(|i| unsafe { + initial_memory.get_f::(addr_space, chunk_ptr + i as u32) }); let initial_hash = hasher.hash(&init_values); - let final_hash = hasher.hash(&values); + let final_hash = hasher.hash(&final_values); FinalTouchedLabel { address_space: addr_space, - label: ptr / CHUNK as u32, + label: chunk_label, init_values, - final_values: values, + final_values, init_hash: initial_hash, final_hash, - final_timestamp: timestamp, + final_timestamps: timestamps, } }) .collect(); @@ -288,7 +334,9 @@ where leaf_label: Val::::from_canonical_u32(touched_label.label), values: touched_label.init_values, hash: touched_label.init_hash, - timestamp: Val::::from_canonical_u32(INITIAL_TIMESTAMP), + // Initial timestamps are all 0 (INITIAL_TIMESTAMP) + timestamps: [Val::::from_canonical_u32(INITIAL_TIMESTAMP); + BLOCKS_PER_CHUNK], }; *final_row.borrow_mut() = PersistentBoundaryCols { @@ -297,7 +345,10 @@ where leaf_label: Val::::from_canonical_u32(touched_label.label), values: touched_label.final_values, hash: touched_label.final_hash, - timestamp: Val::::from_canonical_u32(touched_label.final_timestamp), + // Per-block timestamps - untouched blocks stay at 0 + timestamps: touched_label + .final_timestamps + .map(Val::::from_canonical_u32), }; }); Arc::new(RowMajorMatrix::new(rows, width)) diff --git a/crates/vm/src/system/mod.rs b/crates/vm/src/system/mod.rs index d1ecb2daf1..195fa6a701 100644 --- a/crates/vm/src/system/mod.rs +++ b/crates/vm/src/system/mod.rs @@ -32,7 +32,8 @@ use crate::{ ChipInventoryError, DenseRecordArena, ExecutionBridge, ExecutionBus, ExecutionState, ExecutorInventory, ExecutorInventoryError, MatrixRecordArena, PhantomSubExecutor, RowMajorMatrixArena, SystemConfig, VmAirWrapper, VmBuilder, VmChipComplex, VmChipWrapper, - VmCircuitConfig, VmExecutionConfig, CONNECTOR_AIR_ID, PROGRAM_AIR_ID, PUBLIC_VALUES_AIR_ID, + VmCircuitConfig, VmExecutionConfig, CONNECTOR_AIR_ID, CONST_BLOCK_SIZE, PROGRAM_AIR_ID, + PUBLIC_VALUES_AIR_ID, }, system::{ connector::VmConnectorChip, @@ -145,7 +146,7 @@ pub struct SystemRecords { } pub enum TouchedMemory { - Persistent(TimestampedEquipartition), + Persistent(TimestampedEquipartition), Volatile(TimestampedEquipartition), } From 1a0bbec3cae3cfbdfb8019898f6aa7b11872fd19 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 21:38:57 +0000 Subject: [PATCH 05/19] clean up redundant fn signature --- crates/vm/src/system/memory/online.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index 021863c54b..a18d97fa74 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -968,12 +968,10 @@ impl TracingMemory { match is_persistent { false => TouchedMemory::Volatile( - self.touched_blocks_to_equipartition::(touched_blocks), + self.touched_blocks_to_equipartition::(touched_blocks), ), true => TouchedMemory::Persistent( - self.touched_blocks_to_equipartition::( - touched_blocks, - ), + self.touched_blocks_to_equipartition::(touched_blocks), ), } } @@ -1003,23 +1001,15 @@ impl TracingMemory { /// Returns the equipartition of the touched blocks. /// Modifies records and adds new to account for the initial/final segments. - fn touched_blocks_to_equipartition< - F: Field, - const PARTITION_SIZE: usize, - const OUTPUT_SIZE: usize, - >( + fn touched_blocks_to_equipartition( &mut self, touched_blocks: Vec<((u32, u32), AccessMetadata)>, - ) -> TimestampedEquipartition { - assert!( - OUTPUT_SIZE % PARTITION_SIZE == 0, - "Output size must be a multiple of the partition size" - ); + ) -> TimestampedEquipartition { // [perf] We can `.with_capacity()` if we keep track of the number of segments we initialize let mut partitioned_memory = Vec::new(); debug_assert!(touched_blocks.is_sorted_by_key(|(addr, _)| addr)); - self.handle_touched_blocks::(&mut partitioned_memory, touched_blocks); + self.handle_touched_blocks::(&mut partitioned_memory, touched_blocks); debug_assert!(partitioned_memory.is_sorted_by_key(|(key, _)| *key)); partitioned_memory From 14665e02df50633e75e953ae2187150ff3cc6033 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 22:35:29 +0000 Subject: [PATCH 06/19] aot constant fix --- extensions/rv32im/circuit/src/common/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/rv32im/circuit/src/common/mod.rs b/extensions/rv32im/circuit/src/common/mod.rs index 0a58b7310b..5bac384f4b 100644 --- a/extensions/rv32im/circuit/src/common/mod.rs +++ b/extensions/rv32im/circuit/src/common/mod.rs @@ -9,7 +9,7 @@ mod aot { use openvm_circuit::{ arch::{ execution_mode::{metered::memory_ctx::MemoryCtx, MeteredCtx}, - AotError, SystemConfig, VmExecState, ADDR_SPACE_OFFSET, + AotError, SystemConfig, VmExecState, ADDR_SPACE_OFFSET, CONST_BLOCK_SIZE, }, system::memory::{merkle::public_values::PUBLIC_VALUES_AS, online::GuestMemory, CHUNK}, }; @@ -148,7 +148,7 @@ mod aot { // } // } // ``` - // + // // For a specific RV32 instruction, the variables can be treated as constants at AOT // compilation time: // - `address_space`: always a constant because it is derived from an Instruction @@ -225,7 +225,7 @@ mod aot { // } // } // ``` - // + // // For a specific RV32 instruction, the variables can be treated as constants at AOT compilation time: // Inputs: // - `chunk`: always 8(CHUNK) because we only support when continuation is enabled. @@ -244,12 +244,12 @@ mod aot { // Therefore the loop only iterates once for `page_id = start_page_id`. let initial_block_size: usize = config.initial_block_size(); - if initial_block_size != CHUNK { + if initial_block_size != CONST_BLOCK_SIZE { return Err(AotError::Other(format!( - "initial_block_size must be {CHUNK}, got {initial_block_size}" + "initial_block_size must be {CONST_BLOCK_SIZE}, got {initial_block_size}" ))); } - let chunk_bits = CHUNK.ilog2(); + let chunk_bits = CONST_BLOCK_SIZE.ilog2(); let as_offset = ((address_space - ADDR_SPACE_OFFSET) as u64) << (config.memory_config.memory_dimensions().address_height); From 6abaae0ff17adcd55f4057a2dde9d33edbba0f3c Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 23:03:53 +0000 Subject: [PATCH 07/19] LINTA --- crates/vm/src/arch/config.rs | 3 +- .../arch/execution_mode/metered/memory_ctx.rs | 1 - .../src/arch/execution_mode/metered_cost.rs | 1 - crates/vm/src/arch/state.rs | 13 ------- crates/vm/src/arch/testing/cpu.rs | 6 --- crates/vm/src/system/memory/adapter/mod.rs | 7 ---- crates/vm/src/system/memory/online.rs | 38 +++---------------- crates/vm/src/system/memory/persistent.rs | 10 +++-- extensions/rv32im/tests/src/lib.rs | 4 +- 9 files changed, 15 insertions(+), 68 deletions(-) diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 02e790a932..b780021c41 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -187,8 +187,7 @@ pub struct MemoryConfig { /// Maximum N AccessAdapter AIR to support. pub max_access_adapter_n: usize, /// Whether access adapters are enabled. When disabled, all memory accesses must be of the - /// standard block size (e.g., 4 for address spaces 1-3). This removes the need for access - /// adapter AIRs and simplifies the memory system. + /// standard block size (e.g., 4 for address spaces 1-3). #[new(value = "true")] pub access_adapters_enabled: bool, } diff --git a/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs b/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs index d75dc2c46b..d755d73140 100644 --- a/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs +++ b/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs @@ -212,7 +212,6 @@ impl MemoryCtx { size_bits: u32, num: u32, ) { - // Skip if access adapters are disabled if !self.access_adapters_enabled { return; } diff --git a/crates/vm/src/arch/execution_mode/metered_cost.rs b/crates/vm/src/arch/execution_mode/metered_cost.rs index c92965ad3f..69bfd6fe69 100644 --- a/crates/vm/src/arch/execution_mode/metered_cost.rs +++ b/crates/vm/src/arch/execution_mode/metered_cost.rs @@ -38,7 +38,6 @@ impl AccessAdapterCtx { size_bits: u32, widths: &[usize], ) { - // Skip if access adapters are disabled if !self.enabled { return; } diff --git a/crates/vm/src/arch/state.rs b/crates/vm/src/arch/state.rs index 1da2a4d392..42751d1e6a 100644 --- a/crates/vm/src/arch/state.rs +++ b/crates/vm/src/arch/state.rs @@ -1,5 +1,4 @@ use std::{ - backtrace::Backtrace, fmt::Debug, ops::{Deref, DerefMut}, }; @@ -188,12 +187,6 @@ where addr_space: u32, ptr: u32, ) -> [T; BLOCK_SIZE] { - if BLOCK_SIZE != CONST_BLOCK_SIZE { - println!( - "vm_read: addr_space = {}, ptr = {}, BLOCK_SIZE = {}", - addr_space, ptr, BLOCK_SIZE - ); - } self.ctx .on_memory_operation(addr_space, ptr, BLOCK_SIZE as u32); self.host_read(addr_space, ptr) @@ -207,12 +200,6 @@ where ptr: u32, data: &[T; BLOCK_SIZE], ) { - if BLOCK_SIZE != CONST_BLOCK_SIZE { - println!( - "vm_read: addr_space = {}, ptr = {}, BLOCK_SIZE = {}", - addr_space, ptr, BLOCK_SIZE - ); - } self.ctx .on_memory_operation(addr_space, ptr, BLOCK_SIZE as u32); self.host_write(addr_space, ptr, data) diff --git a/crates/vm/src/arch/testing/cpu.rs b/crates/vm/src/arch/testing/cpu.rs index 4c574dda73..14af328845 100644 --- a/crates/vm/src/arch/testing/cpu.rs +++ b/crates/vm/src/arch/testing/cpu.rs @@ -348,11 +348,6 @@ impl VmChipTestBuilder { pub fn persistent(mem_config: MemoryConfig) -> Self { setup_tracing_with_log_level(Level::INFO); - /// ERRRMMM WHAT THE SIGMA not testing here - println!( - "PERSISTENT MEMORY TESTING, CONST_BLOCK_SIZE = {}", - CONST_BLOCK_SIZE - ); let (range_checker, memory) = Self::range_checker_and_memory(&mem_config, CONST_BLOCK_SIZE); let hasher_chip = Arc::new(Poseidon2PeripheryChip::new( vm_poseidon2_config(), @@ -476,7 +471,6 @@ where let mut memory_controller = memory_tester.controller; let is_persistent = memory_controller.continuation_enabled(); let mut memory = memory_tester.memory; - // here? pass in initial memory for chunking let touched_memory = memory.finalize::>(is_persistent); // Balance memory boundaries let range_checker = memory_controller.range_checker.clone(); diff --git a/crates/vm/src/system/memory/adapter/mod.rs b/crates/vm/src/system/memory/adapter/mod.rs index 2ae0fc28eb..adad5ca60f 100644 --- a/crates/vm/src/system/memory/adapter/mod.rs +++ b/crates/vm/src/system/memory/adapter/mod.rs @@ -1,5 +1,4 @@ use std::{ - backtrace::Backtrace, borrow::{Borrow, BorrowMut}, marker::PhantomData, ptr::copy_nonoverlapping, @@ -134,12 +133,6 @@ impl AccessAdapterInventory { let bytes_slice = &bytes[ptr..]; let header: &AccessRecordHeader = bytes_slice.borrow(); - if header.block_size == 8 { - println!("Found size 8 access:"); - println!(" Address space: {}", header.address_space); - println!(" Pointer: {}", header.pointer); - println!(" Timestamp: {}", header.timestamp_and_mask); - } // SAFETY: // - bytes[ptr..] is a valid starting pointer to a previously allocated record // - The record contains self-describing layout information diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index a18d97fa74..b53eb92d51 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -440,7 +440,7 @@ pub struct TracingMemory { initial_block_size: usize, /// The underlying data memory, with memory cells typed by address space: see [AddressMap]. #[getset(get = "pub")] - pub data: GuestMemory, // is this "initial memory" + pub data: GuestMemory, /// Maps addr_space to (ptr / min_block_size[addr_space] -> AccessMetadata) for latest access /// metadata. Uses paged storage for memory efficiency. AccessMetadata stores offset_to_start /// (in ALIGN units), block_size, and timestamp (latter two only valid at offset_to_start == @@ -577,16 +577,6 @@ impl TracingMemory { } pub(crate) fn add_split_record(&mut self, header: AccessRecordHeader) { - if header.block_size == 8 { - println!("-----SPLIT-----"); - println!("Adding split record for size 8:"); - println!(" Address space: {}", header.address_space); - println!(" Pointer: {}", header.pointer); - println!(" Timestamp: {}", header.timestamp_and_mask); - - let bt = Backtrace::capture(); - println!("{bt}"); - } if header.block_size == header.lowest_block_size { return; } @@ -612,8 +602,6 @@ impl TracingMemory { // we don't mind garbage values in prev_* } - //appears that we are initially still splitting, and merging memory - // is this from merkle tree? /// `data_slice` is the underlying data of the record in raw host memory format. pub(crate) fn add_merge_record( &mut self, @@ -621,16 +609,6 @@ impl TracingMemory { data_slice: &[u8], prev_ts: &[u32], ) { - if header.block_size == 8 { - println!("-----MERGE-----"); - println!("Adding merge record for size 8:"); - println!(" Address space: {}", header.address_space); - println!(" Pointer: {}", header.pointer); - println!(" Timestamp: {}", header.timestamp_and_mask); - - let bt = Backtrace::capture(); - println!("{bt}"); - } if header.block_size == header.lowest_block_size { return; } @@ -717,10 +695,6 @@ impl TracingMemory { AccessMetadata::new(timestamp, MIN_BLOCK_SIZE as u8, 0), ); } - println!( - "BLOCK SIZE: {}, MIN_BLOCK_SIZE: {}", - block_size, MIN_BLOCK_SIZE - ); self.add_split_record(AccessRecordHeader { timestamp_and_mask: timestamp, address_space: address_space as u32, @@ -961,7 +935,6 @@ impl TracingMemory { } /// Finalize the boundary and merkle chips. - /// pass in initial memory,for rechunking #[instrument(name = "memory_finalize", skip_all)] pub fn finalize(&mut self, is_persistent: bool) -> TouchedMemory { let touched_blocks = self.touched_blocks(); @@ -1013,7 +986,6 @@ impl TracingMemory { debug_assert!(partitioned_memory.is_sorted_by_key(|(key, _)| *key)); partitioned_memory - // self.rechunk_final_memory::(partitioned_memory) } fn handle_touched_blocks( @@ -1088,8 +1060,9 @@ impl TracingMemory { timestamp, values: from_fn(|j| { let byte_idx = (i as usize + j) * cell_size; - // SAFETY: block_size is multiple of PARTITION_SIZE and we are reading chunks - // of cells within bounds + // SAFETY: block_size is multiple of PARTITION_SIZE and we are + // reading chunks of cells within + // bounds unsafe { addr_space_config .layout @@ -1112,7 +1085,8 @@ impl TracingMemory { current_values[current_cnt * cell_size..current_cnt * cell_size + cell_size] .copy_from_slice(cell_data); if current_cnt & (min_block_size - 1) == 0 { - // SAFETY: current_cnt / min_block_size < PARTITION_SIZE / min_block_size <= PARTITION_SIZE + // SAFETY: current_cnt / min_block_size < PARTITION_SIZE / min_block_size <= + // PARTITION_SIZE unsafe { *current_timestamps.get_unchecked_mut(current_cnt / min_block_size) = timestamp; diff --git a/crates/vm/src/system/memory/persistent.rs b/crates/vm/src/system/memory/persistent.rs index f3f650fbe0..c30c80895e 100644 --- a/crates/vm/src/system/memory/persistent.rs +++ b/crates/vm/src/system/memory/persistent.rs @@ -48,7 +48,8 @@ pub struct PersistentBoundaryCols { pub values: [T; CHUNK], pub hash: [T; CHUNK], /// Per-block timestamps. Each CONST_BLOCK_SIZE block within the chunk has its own timestamp. - /// For untouched blocks, timestamp stays at 0 (balances: boundary sends at t=0 init, receives at t=0 final). + /// For untouched blocks, timestamp stays at 0 (balances: boundary sends at t=0 init, receives + /// at t=0 final). pub timestamps: [T; BLOCKS_PER_CHUNK], } @@ -228,9 +229,10 @@ impl PersistentBoundaryChip { /// Finalize the boundary chip with per-block timestamped memory. /// - /// `final_memory` is at CONST_BLOCK_SIZE granularity (4 bytes per entry, single timestamp each). - /// This function rechunks into CHUNK-sized (8 bytes) groups with per-block timestamps. - /// Untouched blocks within a touched chunk get values from initial_memory and timestamp 0. + /// `final_memory` is at CONST_BLOCK_SIZE granularity (4 bytes per entry, single timestamp + /// each). This function rechunks into CHUNK-sized (8 bytes) groups with per-block + /// timestamps. Untouched blocks within a touched chunk get values from initial_memory and + /// timestamp 0. #[instrument(name = "boundary_finalize", level = "debug", skip_all)] pub(crate) fn finalize( &mut self, diff --git a/extensions/rv32im/tests/src/lib.rs b/extensions/rv32im/tests/src/lib.rs index f4f0efd0d3..c4302ae808 100644 --- a/extensions/rv32im/tests/src/lib.rs +++ b/extensions/rv32im/tests/src/lib.rs @@ -93,9 +93,9 @@ mod tests { Ok(()) } - // #[test_case("fibonacci", 1)] + #[test_case("fibonacci", 1)] #[test_case("collatz", 1)] - fn test_rv32m(example_name: &str, min_segments: usize) -> Result<()> { + fn test_rv32im(example_name: &str, min_segments: usize) -> Result<()> { let config = test_rv32im_config(); let elf = build_example_program_at_path(get_programs_dir!(), example_name, &config)?; let exe = VmExe::from_elf( From 37b41125d33040f2936844ee38399c8fbea77433 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 23:04:03 +0000 Subject: [PATCH 08/19] LINTA --- extensions/rv32im/circuit/src/common/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/rv32im/circuit/src/common/mod.rs b/extensions/rv32im/circuit/src/common/mod.rs index 5bac384f4b..20855af15d 100644 --- a/extensions/rv32im/circuit/src/common/mod.rs +++ b/extensions/rv32im/circuit/src/common/mod.rs @@ -148,7 +148,7 @@ mod aot { // } // } // ``` - // + // // For a specific RV32 instruction, the variables can be treated as constants at AOT // compilation time: // - `address_space`: always a constant because it is derived from an Instruction @@ -225,7 +225,7 @@ mod aot { // } // } // ``` - // + // // For a specific RV32 instruction, the variables can be treated as constants at AOT compilation time: // Inputs: // - `chunk`: always 8(CHUNK) because we only support when continuation is enabled. From 9f15491ce95b1f167070955baf0d79dfcedd3636 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 23:15:27 +0000 Subject: [PATCH 09/19] gpu? --- crates/vm/src/system/cuda/memory.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/system/cuda/memory.rs b/crates/vm/src/system/cuda/memory.rs index 51d7b3677e..448ec85200 100644 --- a/crates/vm/src/system/cuda/memory.rs +++ b/crates/vm/src/system/cuda/memory.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use openvm_circuit::{ - arch::{AddressSpaceHostLayout, DenseRecordArena, MemoryConfig, ADDR_SPACE_OFFSET}, + arch::{ + AddressSpaceHostLayout, DenseRecordArena, MemoryConfig, ADDR_SPACE_OFFSET, CONST_BLOCK_SIZE, + }, system::{ memory::{online::LinearMemory, AddressMap, TimestampedValues}, TouchedMemory, @@ -150,8 +152,9 @@ impl MemoryInventoryGPU { mem.tracing_info("boundary finalize"); let (touched_memory, empty) = if partition.is_empty() { + // Create a dummy touched memory entry with CONST_BLOCK_SIZE values let leftmost_values = 'left: { - let mut res = [F::ZERO; DIGEST_WIDTH]; + let mut res = [F::ZERO; CONST_BLOCK_SIZE]; if persistent.initial_memory[ADDR_SPACE_OFFSET as usize].is_empty() { break 'left res; } @@ -159,7 +162,7 @@ impl MemoryInventoryGPU { [ADDR_SPACE_OFFSET as usize] .layout; let one_cell_size = layout.size(); - let values = vec![0u8; one_cell_size * DIGEST_WIDTH]; + let values = vec![0u8; one_cell_size * CONST_BLOCK_SIZE]; unsafe { cuda_memcpy::( values.as_ptr() as *mut std::ffi::c_void, @@ -168,7 +171,7 @@ impl MemoryInventoryGPU { values.len(), ) .unwrap(); - for i in 0..DIGEST_WIDTH { + for i in 0..CONST_BLOCK_SIZE { res[i] = layout.to_field::(&values[i * one_cell_size..]); } } From fff79622ef60805e7618faf31660ac91eb80ba82 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 23:18:25 +0000 Subject: [PATCH 10/19] Revert "gpu?" This reverts commit 9c302373e28c0102b76057dede3297f4c2233ced. --- crates/vm/src/system/cuda/memory.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/vm/src/system/cuda/memory.rs b/crates/vm/src/system/cuda/memory.rs index 448ec85200..51d7b3677e 100644 --- a/crates/vm/src/system/cuda/memory.rs +++ b/crates/vm/src/system/cuda/memory.rs @@ -1,9 +1,7 @@ use std::sync::Arc; use openvm_circuit::{ - arch::{ - AddressSpaceHostLayout, DenseRecordArena, MemoryConfig, ADDR_SPACE_OFFSET, CONST_BLOCK_SIZE, - }, + arch::{AddressSpaceHostLayout, DenseRecordArena, MemoryConfig, ADDR_SPACE_OFFSET}, system::{ memory::{online::LinearMemory, AddressMap, TimestampedValues}, TouchedMemory, @@ -152,9 +150,8 @@ impl MemoryInventoryGPU { mem.tracing_info("boundary finalize"); let (touched_memory, empty) = if partition.is_empty() { - // Create a dummy touched memory entry with CONST_BLOCK_SIZE values let leftmost_values = 'left: { - let mut res = [F::ZERO; CONST_BLOCK_SIZE]; + let mut res = [F::ZERO; DIGEST_WIDTH]; if persistent.initial_memory[ADDR_SPACE_OFFSET as usize].is_empty() { break 'left res; } @@ -162,7 +159,7 @@ impl MemoryInventoryGPU { [ADDR_SPACE_OFFSET as usize] .layout; let one_cell_size = layout.size(); - let values = vec![0u8; one_cell_size * CONST_BLOCK_SIZE]; + let values = vec![0u8; one_cell_size * DIGEST_WIDTH]; unsafe { cuda_memcpy::( values.as_ptr() as *mut std::ffi::c_void, @@ -171,7 +168,7 @@ impl MemoryInventoryGPU { values.len(), ) .unwrap(); - for i in 0..CONST_BLOCK_SIZE { + for i in 0..DIGEST_WIDTH { res[i] = layout.to_field::(&values[i * one_cell_size..]); } } From d5442fab07325039c34b28755806877b325b23e4 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 23:26:34 +0000 Subject: [PATCH 11/19] LINTA --- crates/vm/src/arch/testing/cpu.rs | 2 +- crates/vm/src/system/memory/online.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/arch/testing/cpu.rs b/crates/vm/src/arch/testing/cpu.rs index 14af328845..03ef71440b 100644 --- a/crates/vm/src/arch/testing/cpu.rs +++ b/crates/vm/src/arch/testing/cpu.rs @@ -49,7 +49,7 @@ use crate::{ adapter::records::arena_size_bound, offline_checker::{MemoryBridge, MemoryBus}, online::TracingMemory, - MemoryAirInventory, MemoryController, SharedMemoryHelper, CHUNK, + MemoryAirInventory, MemoryController, SharedMemoryHelper, }, poseidon2::Poseidon2PeripheryChip, program::ProgramBus, diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index b53eb92d51..4ba48d9408 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -1,4 +1,4 @@ -use std::{array::from_fn, backtrace::Backtrace, fmt::Debug, num::NonZero}; +use std::{array::from_fn, fmt::Debug, num::NonZero}; use getset::Getters; use itertools::zip_eq; From 23601a8540bbfddd823baaced14275660738bfa0 Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 12 Dec 2025 23:35:05 +0000 Subject: [PATCH 12/19] revert gitignore --- .gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitignore b/.gitignore index 87e918c19e..a252b637e6 100644 --- a/.gitignore +++ b/.gitignore @@ -48,10 +48,3 @@ profile.json.gz # test fixtures benchmarks/fixtures -#TODO: Remove this -crates/toolchain/tests/rv32im-test-vectors/tests/* -*.o -*.a -*.s -*.txt -riscv/* \ No newline at end of file From ec38241f8ff260ff7b0c5c08f1c33e4a3e991055 Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 18 Dec 2025 17:03:56 +0000 Subject: [PATCH 13/19] linta + cleanup --- crates/vm/src/arch/config.rs | 13 +++--- crates/vm/src/arch/state.rs | 2 +- crates/vm/src/arch/testing/cpu.rs | 2 +- crates/vm/src/system/memory/controller/mod.rs | 1 - crates/vm/src/system/memory/online.rs | 46 +++++++++---------- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index b780021c41..f84a73a79b 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -187,7 +187,7 @@ pub struct MemoryConfig { /// Maximum N AccessAdapter AIR to support. pub max_access_adapter_n: usize, /// Whether access adapters are enabled. When disabled, all memory accesses must be of the - /// standard block size (e.g., 4 for address spaces 1-3). + /// standard block size (ie, 4 for address spaces 1-3). #[new(value = "true")] pub access_adapters_enabled: bool, } @@ -261,8 +261,8 @@ impl MemoryConfig { .collect() } - /// Returns true if the Native address space (AS 4) is used. - /// Native AS is considered "used" if it has any allocated cells. + /// Returns true if the Native address space (AS 4) is used + /// Native AS is considered "used" if it has any allocated cells pub fn is_native_as_used(&self) -> bool { self.addr_spaces .get(NATIVE_AS as usize) @@ -277,7 +277,7 @@ impl MemoryConfig { self } - /// Enables access adapters. This is the default behavior. + /// Enables access adapters. This is the default behavior pub fn with_access_adapters(mut self) -> Self { self.access_adapters_enabled = true; self @@ -285,7 +285,7 @@ impl MemoryConfig { /// Automatically sets `access_adapters_enabled` based on whether Native AS is used. /// If Native AS is not used, access adapters are disabled since all other address spaces - /// use a fixed block size of 4. + /// use a fixed block size of 4 pub fn with_auto_access_adapters(mut self) -> Self { self.access_adapters_enabled = self.is_native_as_used(); self @@ -432,8 +432,7 @@ impl SystemConfig { } /// Disables access adapters. When disabled, all memory accesses for address spaces 1-3 - /// must use the constant block size (4). This simplifies the memory system by removing - /// access adapter AIRs. + /// must use the constant block size (4) pub fn without_access_adapters(mut self) -> Self { self.memory_config.access_adapters_enabled = false; self diff --git a/crates/vm/src/arch/state.rs b/crates/vm/src/arch/state.rs index 42751d1e6a..6e79677541 100644 --- a/crates/vm/src/arch/state.rs +++ b/crates/vm/src/arch/state.rs @@ -13,7 +13,7 @@ use super::{create_memory_image, ExecutionError, Streams}; #[cfg(feature = "metrics")] use crate::metrics::VmMetrics; use crate::{ - arch::{execution_mode::ExecutionCtxTrait, SystemConfig, VmStateMut, CONST_BLOCK_SIZE}, + arch::{execution_mode::ExecutionCtxTrait, SystemConfig, VmStateMut}, system::memory::online::GuestMemory, }; diff --git a/crates/vm/src/arch/testing/cpu.rs b/crates/vm/src/arch/testing/cpu.rs index 03ef71440b..d818dee460 100644 --- a/crates/vm/src/arch/testing/cpu.rs +++ b/crates/vm/src/arch/testing/cpu.rs @@ -333,7 +333,7 @@ impl VmChipTestBuilder { fn range_checker_and_memory( mem_config: &MemoryConfig, - init_block_size: usize, // modify this to CONST_BLOCK_SIZE + init_block_size: usize, ) -> (SharedVariableRangeCheckerChip, TracingMemory) { let range_checker = Arc::new(VariableRangeCheckerChip::new(VariableRangeCheckerBus::new( RANGE_CHECKER_BUS, diff --git a/crates/vm/src/system/memory/controller/mod.rs b/crates/vm/src/system/memory/controller/mod.rs index bd770d163e..e4733ccdf4 100644 --- a/crates/vm/src/system/memory/controller/mod.rs +++ b/crates/vm/src/system/memory/controller/mod.rs @@ -289,7 +289,6 @@ impl MemoryController { TouchedMemory::Persistent(final_memory), ) => { let hasher = self.hasher_chip.as_ref().unwrap(); - // boundary_chip.finalize takes CONST_BLOCK_SIZE granularity and rechunks internally boundary_chip.finalize(initial_memory, &final_memory, hasher.as_ref()); // Rechunk CONST_BLOCK_SIZE blocks into CHUNK-sized blocks for merkle_chip diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index 4ba48d9408..44d8256ab1 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -979,24 +979,24 @@ impl TracingMemory { touched_blocks: Vec<((u32, u32), AccessMetadata)>, ) -> TimestampedEquipartition { // [perf] We can `.with_capacity()` if we keep track of the number of segments we initialize - let mut partitioned_memory = Vec::new(); + let mut final_memory = Vec::new(); debug_assert!(touched_blocks.is_sorted_by_key(|(addr, _)| addr)); - self.handle_touched_blocks::(&mut partitioned_memory, touched_blocks); + self.handle_touched_blocks::(&mut final_memory, touched_blocks); - debug_assert!(partitioned_memory.is_sorted_by_key(|(key, _)| *key)); - partitioned_memory + debug_assert!(final_memory.is_sorted_by_key(|(key, _)| *key)); + final_memory } - fn handle_touched_blocks( + fn handle_touched_blocks( &mut self, - final_memory: &mut Vec<((u32, u32), TimestampedValues)>, + final_memory: &mut Vec<((u32, u32), TimestampedValues)>, touched_blocks: Vec<((u32, u32), AccessMetadata)>, ) { - let mut current_values = vec![0u8; MAX_CELL_BYTE_SIZE * PARTITION_SIZE]; + let mut current_values = vec![0u8; MAX_CELL_BYTE_SIZE * CHUNK]; let mut current_cnt = 0; let mut current_address = MemoryAddress::new(0, 0); - let mut current_timestamps = vec![0; PARTITION_SIZE]; + let mut current_timestamps = vec![0; CHUNK]; for ((addr_space, ptr), access_metadata) in touched_blocks { // SAFETY: addr_space of touched blocks are all in bounds let addr_space_config = @@ -1009,16 +1009,16 @@ impl TracingMemory { current_cnt == 0 || (current_address.address_space == addr_space && current_address.pointer + current_cnt as u32 == ptr), - "The union of all touched blocks must consist of blocks with sizes divisible by the partition size" + "The union of all touched blocks must consist of blocks with sizes divisible by the `CHUNK`" ); debug_assert!(block_size >= min_block_size as u8); debug_assert!(ptr % min_block_size as u32 == 0); if current_cnt == 0 { assert_eq!( - ptr & (PARTITION_SIZE as u32 - 1), + ptr & (CHUNK as u32 - 1), 0, - "The union of all touched blocks must consist of partition-aligned blocks" + "The union of all touched blocks must consist of `CHUNK`-aligned blocks" ); current_address = MemoryAddress::new(addr_space, ptr); } @@ -1033,7 +1033,7 @@ impl TracingMemory { type_size: cell_size as u32, }); } - if min_block_size > PARTITION_SIZE { + if min_block_size > CHUNK { assert_eq!(current_cnt, 0); for i in (0..block_size as u32).step_by(min_block_size) { self.add_split_record(AccessRecordHeader { @@ -1041,7 +1041,7 @@ impl TracingMemory { address_space: addr_space, pointer: ptr + i, block_size: min_block_size as u32, - lowest_block_size: PARTITION_SIZE as u32, + lowest_block_size: CHUNK as u32, type_size: cell_size as u32, }); } @@ -1053,14 +1053,14 @@ impl TracingMemory { block_size as usize * cell_size, ) }; - for i in (0..block_size as u32).step_by(PARTITION_SIZE) { + for i in (0..block_size as u32).step_by(CHUNK) { final_memory.push(( (addr_space, ptr + i), TimestampedValues { timestamp, values: from_fn(|j| { let byte_idx = (i as usize + j) * cell_size; - // SAFETY: block_size is multiple of PARTITION_SIZE and we are + // SAFETY: block_size is multiple of CHUNK and we are // reading chunks of cells within // bounds unsafe { @@ -1085,16 +1085,16 @@ impl TracingMemory { current_values[current_cnt * cell_size..current_cnt * cell_size + cell_size] .copy_from_slice(cell_data); if current_cnt & (min_block_size - 1) == 0 { - // SAFETY: current_cnt / min_block_size < PARTITION_SIZE / min_block_size <= - // PARTITION_SIZE + // SAFETY: current_cnt / min_block_size < CHUNK / min_block_size <= + // CHUNKs unsafe { *current_timestamps.get_unchecked_mut(current_cnt / min_block_size) = timestamp; } } current_cnt += 1; - if current_cnt == PARTITION_SIZE { - let timestamp = *current_timestamps[..PARTITION_SIZE / min_block_size] + if current_cnt == CHUNK { + let timestamp = *current_timestamps[..CHUNK / min_block_size] .iter() .max() .unwrap(); @@ -1103,12 +1103,12 @@ impl TracingMemory { timestamp_and_mask: timestamp, address_space: addr_space, pointer: current_address.pointer, - block_size: PARTITION_SIZE as u32, + block_size: CHUNK as u32, lowest_block_size: min_block_size as u32, type_size: cell_size as u32, }, - ¤t_values[..PARTITION_SIZE * cell_size], - ¤t_timestamps[..PARTITION_SIZE / min_block_size], + ¤t_values[..CHUNK * cell_size], + ¤t_timestamps[..CHUNK / min_block_size], ); final_memory.push(( (current_address.address_space, current_address.pointer), @@ -1130,7 +1130,7 @@ impl TracingMemory { } assert_eq!( current_cnt, 0, - "The union of all touched blocks must consist of blocks with sizes divisible by the partition size" + "The union of all touched blocks must consist of blocks with sizes divisible by the `CHUNK`" ); } From af24ee4ba8502e810649ab05c4f9848bb172c0b8 Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 18 Dec 2025 17:07:17 +0000 Subject: [PATCH 14/19] gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index a252b637e6..87e918c19e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,10 @@ profile.json.gz # test fixtures benchmarks/fixtures +#TODO: Remove this +crates/toolchain/tests/rv32im-test-vectors/tests/* +*.o +*.a +*.s +*.txt +riscv/* \ No newline at end of file From b90af0a2b611764cc6c68d3cbcb8622d2f057f7a Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 18 Dec 2025 18:44:48 +0000 Subject: [PATCH 15/19] testing rv32 with access_adapters disabled --- crates/vm/src/arch/execution_mode/metered/ctx.rs | 12 +++++++----- .../src/arch/execution_mode/metered/memory_ctx.rs | 2 +- crates/vm/src/arch/testing/cpu.rs | 2 +- crates/vm/src/arch/vm.rs | 8 +++++++- crates/vm/src/system/memory/online.rs | 3 ++- extensions/rv32im/circuit/src/base_alu/tests.rs | 14 ++++++++++---- extensions/rv32im/circuit/src/loadstore/tests.rs | 14 ++++++++++---- extensions/rv32im/tests/src/lib.rs | 2 +- 8 files changed, 39 insertions(+), 18 deletions(-) diff --git a/crates/vm/src/arch/execution_mode/metered/ctx.rs b/crates/vm/src/arch/execution_mode/metered/ctx.rs index 8428438ca7..0b67a3b92d 100644 --- a/crates/vm/src/arch/execution_mode/metered/ctx.rs +++ b/crates/vm/src/arch/execution_mode/metered/ctx.rs @@ -64,11 +64,13 @@ impl MeteredCtx { air_names[merkle_tree_index] ); } - debug_assert!( - air_names[memory_ctx.adapter_offset].contains("AccessAdapterAir<2>"), - "air_name={}", - air_names[memory_ctx.adapter_offset] - ); + if memory_ctx.access_adapters_enabled { + debug_assert!( + air_names[memory_ctx.adapter_offset].contains("AccessAdapterAir<2>"), + "air_name={}", + air_names[memory_ctx.adapter_offset] + ); + } let segmentation_ctx = SegmentationCtx::new(air_names, widths, interactions, config.segmentation_limits); diff --git a/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs b/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs index d755d73140..2397b1b12e 100644 --- a/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs +++ b/crates/vm/src/arch/execution_mode/metered/memory_ctx.rs @@ -105,7 +105,7 @@ pub struct MemoryCtx { pub boundary_idx: usize, pub merkle_tree_index: Option, pub adapter_offset: usize, - access_adapters_enabled: bool, + pub access_adapters_enabled: bool, continuations_enabled: bool, chunk: u32, chunk_bits: u32, diff --git a/crates/vm/src/arch/testing/cpu.rs b/crates/vm/src/arch/testing/cpu.rs index d818dee460..e5579fd22c 100644 --- a/crates/vm/src/arch/testing/cpu.rs +++ b/crates/vm/src/arch/testing/cpu.rs @@ -404,7 +404,7 @@ impl Default for VmChipTestBuilder { // removed when tests are updated. mem_config.addr_spaces[RV32_REGISTER_AS as usize].num_cells = 1 << 29; mem_config.addr_spaces[NATIVE_AS as usize].num_cells = 0; - Self::volatile(mem_config) + Self::persistent(mem_config) } } diff --git a/crates/vm/src/arch/vm.rs b/crates/vm/src/arch/vm.rs index 68555050fe..23db960d6d 100644 --- a/crates/vm/src/arch/vm.rs +++ b/crates/vm/src/arch/vm.rs @@ -622,7 +622,13 @@ where let system_config: &SystemConfig = self.config().as_ref(); let adapter_offset = system_config.access_adapter_air_id_offset(); // ATTENTION: this must agree with `num_memory_airs` - let num_adapters = log2_strict_usize(system_config.memory_config.max_access_adapter_n); + + let num_adapters = if system_config.memory_config.access_adapters_enabled { + log2_strict_usize(system_config.memory_config.max_access_adapter_n) + } else { + 0 + }; + assert_eq!(adapter_offset + num_adapters, system_config.num_airs()); let access_adapter_arena_size_bound = records::arena_size_bound( &trace_heights[adapter_offset..adapter_offset + num_adapters], diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index 44d8256ab1..d04e0cbac1 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -580,6 +580,7 @@ impl TracingMemory { if header.block_size == header.lowest_block_size { return; } + assert_eq!(1, 0); // SAFETY: // - header.address_space is validated during instruction decoding and within bounds // - header.pointer and header.type_size define valid memory bounds within the address space @@ -612,7 +613,7 @@ impl TracingMemory { if header.block_size == header.lowest_block_size { return; } - + assert_eq!(1, 0); let record_mut = self .access_adapter_records .alloc(AccessLayout::from_record_header(&header)); diff --git a/extensions/rv32im/circuit/src/base_alu/tests.rs b/extensions/rv32im/circuit/src/base_alu/tests.rs index 8f38dea1f5..7e42b38989 100644 --- a/extensions/rv32im/circuit/src/base_alu/tests.rs +++ b/extensions/rv32im/circuit/src/base_alu/tests.rs @@ -163,8 +163,11 @@ fn rand_rv32_alu_test(opcode: BaseAluOpcode, num_ops: usize) { // TODO(AG): make a more meaningful test for memory accesses tester.write(2, 1024, [F::ONE; 4]); tester.write(2, 1028, [F::ONE; 4]); - let sm = tester.read(2, 1024); - assert_eq!(sm, [F::ONE; 8]); + // Avoid wider-than-min-block accesses when access adapters are disabled + let sm1 = tester.read(2, 1024); + let sm2 = tester.read(2, 1028); + assert_eq!(sm1, [F::ONE; 4]); + assert_eq!(sm2, [F::ONE; 4]); for _ in 0..num_ops { set_and_execute( @@ -201,8 +204,11 @@ fn rand_rv32_alu_test_persistent(opcode: BaseAluOpcode, num_ops: usize) { // TODO(AG): make a more meaningful test for memory accesses tester.write(2, 1024, [F::ONE; 4]); tester.write(2, 1028, [F::ONE; 4]); - let sm = tester.read(2, 1024); - assert_eq!(sm, [F::ONE; 8]); + // Avoid wider-than-min-block accesses when access adapters are disabled + let sm1 = tester.read(2, 1024); + let sm2 = tester.read(2, 1028); + assert_eq!(sm1, [F::ONE; 4]); + assert_eq!(sm2, [F::ONE; 4]); for _ in 0..num_ops { set_and_execute( diff --git a/extensions/rv32im/circuit/src/loadstore/tests.rs b/extensions/rv32im/circuit/src/loadstore/tests.rs index 240da983d0..9d348e9fe5 100644 --- a/extensions/rv32im/circuit/src/loadstore/tests.rs +++ b/extensions/rv32im/circuit/src/loadstore/tests.rs @@ -10,7 +10,7 @@ use openvm_circuit::{ }, }; use openvm_circuit_primitives::var_range::VariableRangeCheckerChip; -use openvm_instructions::{instruction::Instruction, riscv::RV32_REGISTER_AS, LocalOpcode}; +use openvm_instructions::{instruction::Instruction, riscv::RV32_REGISTER_AS, LocalOpcode, NATIVE_AS}; use openvm_rv32im_transpiler::Rv32LoadStoreOpcode::{self, *}; use openvm_stark_backend::{ p3_air::BaseAir, @@ -131,7 +131,8 @@ fn set_and_execute>( let mem_as = mem_as.unwrap_or(if is_load { 2 } else { - *[2, 3, 4].choose(rng).unwrap() + // Avoid Native AS while access adapters are disabled. + *[2, 3].choose(rng).unwrap() }); let shift_amount = ptr_val % 4; @@ -215,10 +216,13 @@ fn rand_loadstore_test(opcode: Rv32LoadStoreOpcode, num_ops: usize) { let mut rng = create_seeded_rng(); let mut mem_config = MemoryConfig::default(); mem_config.addr_spaces[RV32_REGISTER_AS as usize].num_cells = 1 << 29; + mem_config.addr_spaces[NATIVE_AS as usize].num_cells = 0; if [STOREW, STOREB, STOREH].contains(&opcode) { mem_config.addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = 1 << 29; } - let mut tester = VmChipTestBuilder::volatile(mem_config); + // Use persistent memory so initial block size matches the 4-byte alignment and + // avoids access-adapter split/merge paths when adapters are disabled. + let mut tester = VmChipTestBuilder::persistent(mem_config); let mut harness = create_harness(&mut tester); for _ in 0..num_ops { @@ -268,10 +272,12 @@ fn run_negative_loadstore_test( let mut rng = create_seeded_rng(); let mut mem_config = MemoryConfig::default(); mem_config.addr_spaces[RV32_REGISTER_AS as usize].num_cells = 1 << 29; + mem_config.addr_spaces[NATIVE_AS as usize].num_cells = 0; if [STOREW, STOREB, STOREH].contains(&opcode) { mem_config.addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = 1 << 29; } - let mut tester = VmChipTestBuilder::volatile(mem_config); + // Use persistent memory so the min block size matches alignment without needing adapters. + let mut tester = VmChipTestBuilder::persistent(mem_config); let mut harness = create_harness(&mut tester); set_and_execute( diff --git a/extensions/rv32im/tests/src/lib.rs b/extensions/rv32im/tests/src/lib.rs index c4302ae808..32afb14a65 100644 --- a/extensions/rv32im/tests/src/lib.rs +++ b/extensions/rv32im/tests/src/lib.rs @@ -33,7 +33,7 @@ mod tests { fn test_rv32im_config() -> Rv32ImConfig { Rv32ImConfig { rv32i: Rv32IConfig { - system: test_system_config(), + system: test_system_config().without_access_adapters(), ..Default::default() }, ..Default::default() From 03955e4a0061519ccbfa2f2983c46ac8f8a3b645 Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 18 Dec 2025 19:23:40 +0000 Subject: [PATCH 16/19] clean up, remove assert(false) --- crates/vm/src/system/memory/online.rs | 17 +++++------------ extensions/rv32im/tests/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index d04e0cbac1..800d97c214 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -580,7 +580,6 @@ impl TracingMemory { if header.block_size == header.lowest_block_size { return; } - assert_eq!(1, 0); // SAFETY: // - header.address_space is validated during instruction decoding and within bounds // - header.pointer and header.type_size define valid memory bounds within the address space @@ -613,7 +612,6 @@ impl TracingMemory { if header.block_size == header.lowest_block_size { return; } - assert_eq!(1, 0); let record_mut = self .access_adapter_records .alloc(AccessLayout::from_record_header(&header)); @@ -1010,7 +1008,7 @@ impl TracingMemory { current_cnt == 0 || (current_address.address_space == addr_space && current_address.pointer + current_cnt as u32 == ptr), - "The union of all touched blocks must consist of blocks with sizes divisible by the `CHUNK`" + "The union of all touched blocks must consist of blocks with sizes divisible by `CHUNK`" ); debug_assert!(block_size >= min_block_size as u8); debug_assert!(ptr % min_block_size as u32 == 0); @@ -1061,9 +1059,8 @@ impl TracingMemory { timestamp, values: from_fn(|j| { let byte_idx = (i as usize + j) * cell_size; - // SAFETY: block_size is multiple of CHUNK and we are - // reading chunks of cells within - // bounds + // SAFETY: block_size is multiple of CHUNK and we are reading chunks + // of cells within bounds unsafe { addr_space_config .layout @@ -1086,8 +1083,7 @@ impl TracingMemory { current_values[current_cnt * cell_size..current_cnt * cell_size + cell_size] .copy_from_slice(cell_data); if current_cnt & (min_block_size - 1) == 0 { - // SAFETY: current_cnt / min_block_size < CHUNK / min_block_size <= - // CHUNKs + // SAFETY: current_cnt / min_block_size < CHUNK / min_block_size <= CHUNK unsafe { *current_timestamps.get_unchecked_mut(current_cnt / min_block_size) = timestamp; @@ -1129,10 +1125,7 @@ impl TracingMemory { } } } - assert_eq!( - current_cnt, 0, - "The union of all touched blocks must consist of blocks with sizes divisible by the `CHUNK`" - ); + assert_eq!(current_cnt, 0, "The union of all touched blocks must consist of blocks with sizes divisible by `CHUNK`"); } pub fn address_space_alignment(&self) -> Vec { diff --git a/extensions/rv32im/tests/src/lib.rs b/extensions/rv32im/tests/src/lib.rs index 32afb14a65..c4302ae808 100644 --- a/extensions/rv32im/tests/src/lib.rs +++ b/extensions/rv32im/tests/src/lib.rs @@ -33,7 +33,7 @@ mod tests { fn test_rv32im_config() -> Rv32ImConfig { Rv32ImConfig { rv32i: Rv32IConfig { - system: test_system_config().without_access_adapters(), + system: test_system_config(), ..Default::default() }, ..Default::default() From 39d41e026a255369676abb3e9948612d48ae456e Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 18 Dec 2025 19:26:30 +0000 Subject: [PATCH 17/19] whitespace --- crates/vm/src/system/memory/adapter/mod.rs | 1 - crates/vm/src/system/memory/online.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/src/system/memory/adapter/mod.rs b/crates/vm/src/system/memory/adapter/mod.rs index adad5ca60f..a9c89fc2ea 100644 --- a/crates/vm/src/system/memory/adapter/mod.rs +++ b/crates/vm/src/system/memory/adapter/mod.rs @@ -132,7 +132,6 @@ impl AccessAdapterInventory { while ptr < bytes.len() { let bytes_slice = &bytes[ptr..]; let header: &AccessRecordHeader = bytes_slice.borrow(); - // SAFETY: // - bytes[ptr..] is a valid starting pointer to a previously allocated record // - The record contains self-describing layout information diff --git a/crates/vm/src/system/memory/online.rs b/crates/vm/src/system/memory/online.rs index 800d97c214..fb66845c0b 100644 --- a/crates/vm/src/system/memory/online.rs +++ b/crates/vm/src/system/memory/online.rs @@ -612,6 +612,7 @@ impl TracingMemory { if header.block_size == header.lowest_block_size { return; } + let record_mut = self .access_adapter_records .alloc(AccessLayout::from_record_header(&header)); From a5814d48fcaf53f5551b932191a3b6de8bc19007 Mon Sep 17 00:00:00 2001 From: Maillew Date: Thu, 18 Dec 2025 19:32:46 +0000 Subject: [PATCH 18/19] linta --- extensions/rv32im/circuit/src/loadstore/tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/rv32im/circuit/src/loadstore/tests.rs b/extensions/rv32im/circuit/src/loadstore/tests.rs index 9d348e9fe5..978c0cd856 100644 --- a/extensions/rv32im/circuit/src/loadstore/tests.rs +++ b/extensions/rv32im/circuit/src/loadstore/tests.rs @@ -10,7 +10,9 @@ use openvm_circuit::{ }, }; use openvm_circuit_primitives::var_range::VariableRangeCheckerChip; -use openvm_instructions::{instruction::Instruction, riscv::RV32_REGISTER_AS, LocalOpcode, NATIVE_AS}; +use openvm_instructions::{ + instruction::Instruction, riscv::RV32_REGISTER_AS, LocalOpcode, NATIVE_AS, +}; use openvm_rv32im_transpiler::Rv32LoadStoreOpcode::{self, *}; use openvm_stark_backend::{ p3_air::BaseAir, From 5bd398c0a3d2037f9249119c0fe59f3328bc768d Mon Sep 17 00:00:00 2001 From: Maillew Date: Fri, 19 Dec 2025 22:54:08 +0000 Subject: [PATCH 19/19] pr comments --- crates/vm/src/arch/config.rs | 2 +- crates/vm/src/arch/testing/cpu.rs | 1 + crates/vm/src/system/memory/persistent.rs | 71 ++++++++++--------- .../rv32im/circuit/src/loadstore/tests.rs | 1 + 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index f84a73a79b..13f23a45ae 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -124,7 +124,7 @@ const DEFAULT_NATIVE_BLOCK_SIZE: usize = 1; /// The constant block size used for memory accesses when access adapters are disabled. /// All memory accesses for address spaces 1-3 must use this block size. /// This is also the block size used by the Boundary AIR for memory bus interactions. -pub const CONST_BLOCK_SIZE: usize = 4; +pub const CONST_BLOCK_SIZE: usize = DEFAULT_U8_BLOCK_SIZE; /// Trait for generating a init.rs file that contains a call to moduli_init!, /// complex_init!, sw_init! with the supported moduli and curves. diff --git a/crates/vm/src/arch/testing/cpu.rs b/crates/vm/src/arch/testing/cpu.rs index e5579fd22c..cff813801d 100644 --- a/crates/vm/src/arch/testing/cpu.rs +++ b/crates/vm/src/arch/testing/cpu.rs @@ -324,6 +324,7 @@ impl VmChipTestBuilder { let mut mem_config = MemoryConfig::default(); mem_config.addr_spaces[RV32_REGISTER_AS as usize].num_cells = 1 << 29; mem_config.addr_spaces[NATIVE_AS as usize].num_cells = 0; + // TODO: Check if need to revert to volatile memory, after access adapters are removed Self::persistent(mem_config) } diff --git a/crates/vm/src/system/memory/persistent.rs b/crates/vm/src/system/memory/persistent.rs index c30c80895e..cb2be60c6b 100644 --- a/crates/vm/src/system/memory/persistent.rs +++ b/crates/vm/src/system/memory/persistent.rs @@ -24,15 +24,15 @@ use super::{merkle::SerialReceiver, online::INITIAL_TIMESTAMP}; use crate::{ arch::{hasher::Hasher, ADDR_SPACE_OFFSET, CONST_BLOCK_SIZE}, system::memory::{ - dimensions::MemoryDimensions, offline_checker::MemoryBus, MemoryAddress, MemoryImage, - TimestampedEquipartition, + controller::CHUNK, dimensions::MemoryDimensions, offline_checker::MemoryBus, MemoryAddress, + MemoryImage, TimestampedEquipartition, }, }; /// Number of CONST_BLOCK_SIZE blocks per CHUNK (e.g., 2 for 8/4). /// Blocks are on the same row only for Merkle tree hashing (8 bytes at a time). /// Memory bus interactions use per-block timestamps. -pub const BLOCKS_PER_CHUNK: usize = 2; +pub const BLOCKS_PER_CHUNK: usize = CHUNK / CONST_BLOCK_SIZE; /// The values describe aligned chunk of memory of size `CHUNK`---the data together with the last /// accessed timestamp---in either the initial or final memory state. @@ -119,8 +119,6 @@ impl Air for PersistentBoundaryA local.expand_direction * local.expand_direction, ); - debug_assert_eq!(CHUNK % CONST_BLOCK_SIZE, 0); - debug_assert_eq!(CHUNK / CONST_BLOCK_SIZE, BLOCKS_PER_CHUNK); let chunk_size_f = AB::F::from_canonical_usize(CHUNK); for block_idx in 0..BLOCKS_PER_CHUNK { let offset = AB::F::from_canonical_usize(block_idx * CONST_BLOCK_SIZE); @@ -243,43 +241,48 @@ impl PersistentBoundaryChip { ) where H: Hasher + Sync + for<'a> SerialReceiver<&'a [F]>, { - // Group CONST_BLOCK_SIZE blocks into CHUNK-sized groups - // Key: (addr_space, chunk_label), Value: per-block timestamps and values - use std::collections::BTreeMap; - let mut chunk_map: BTreeMap<(u32, u32), ([u32; BLOCKS_PER_CHUNK], [F; CHUNK])> = - BTreeMap::new(); - - for &((addr_space, ptr), ts_values) in final_memory.iter() { - let chunk_label = ptr / CHUNK as u32; - let block_idx_in_chunk = ((ptr % CHUNK as u32) / CONST_BLOCK_SIZE as u32) as usize; - - let entry = chunk_map - .entry((addr_space, chunk_label)) - .or_insert_with(|| { - // Initialize with values from initial memory and timestamps at 0 - let chunk_ptr = chunk_label * CHUNK as u32; - let init_values: [F; CHUNK] = array::from_fn(|i| unsafe { - initial_memory.get_f::(addr_space, chunk_ptr + i as u32) - }); - ([0u32; BLOCKS_PER_CHUNK], init_values) - }); + type BlockInfo = (usize, u32, [F; CONST_BLOCK_SIZE]); // (block_idx, timestamp, values) + type EnrichedEntry = ((u32, u32), BlockInfo); // (chunk_key, block_info) + + let enriched: Vec> = final_memory + .par_iter() + .map(|&((addr_space, ptr), ts_values)| { + let chunk_label = ptr / CHUNK as u32; + let block_idx = ((ptr % CHUNK as u32) / CONST_BLOCK_SIZE as u32) as usize; + let key = (addr_space, chunk_label); + let block_info = (block_idx, ts_values.timestamp, ts_values.values); + (key, block_info) + }) + .collect(); - // Set per-block timestamp - entry.0[block_idx_in_chunk] = ts_values.timestamp; - // Copy values for this block - for (i, &val) in ts_values.values.iter().enumerate() { - entry.1[block_idx_in_chunk * CONST_BLOCK_SIZE + i] = val; - } - } + let chunk_groups: Vec<_> = enriched + .chunk_by(|a, b| a.0 == b.0) + .map(|group| { + let key = group[0].0; + let blocks: Vec> = group.iter().map(|&(_, info)| info).collect(); + (key, blocks) + }) + .collect(); - let final_touched_labels: Vec<_> = chunk_map + let final_touched_labels: Vec<_> = chunk_groups .into_par_iter() - .map(|((addr_space, chunk_label), (timestamps, final_values))| { + .map(|((addr_space, chunk_label), blocks)| { let chunk_ptr = chunk_label * CHUNK as u32; // SAFETY: addr_space from `final_memory` are all in bounds let init_values: [F; CHUNK] = array::from_fn(|i| unsafe { initial_memory.get_f::(addr_space, chunk_ptr + i as u32) }); + + let mut final_values = init_values; + let mut timestamps = [0u32; BLOCKS_PER_CHUNK]; + + for (block_idx, ts, values) in blocks { + timestamps[block_idx] = ts; + for (i, &val) in values.iter().enumerate() { + final_values[block_idx * CONST_BLOCK_SIZE + i] = val; + } + } + let initial_hash = hasher.hash(&init_values); let final_hash = hasher.hash(&final_values); FinalTouchedLabel { diff --git a/extensions/rv32im/circuit/src/loadstore/tests.rs b/extensions/rv32im/circuit/src/loadstore/tests.rs index 978c0cd856..1157012212 100644 --- a/extensions/rv32im/circuit/src/loadstore/tests.rs +++ b/extensions/rv32im/circuit/src/loadstore/tests.rs @@ -134,6 +134,7 @@ fn set_and_execute>( 2 } else { // Avoid Native AS while access adapters are disabled. + // TODO: Revert this to [2, 3, 4] when access adapters are removed *[2, 3].choose(rng).unwrap() });