@@ -2,7 +2,10 @@ use std::{fs::File, io::Write, path::Path};
22
33use derive_new:: new;
44use getset:: { Setters , WithSetters } ;
5- use openvm_instructions:: NATIVE_AS ;
5+ use openvm_instructions:: {
6+ riscv:: { RV32_IMM_AS , RV32_MEMORY_AS , RV32_REGISTER_AS } ,
7+ NATIVE_AS ,
8+ } ;
69use openvm_poseidon2_air:: Poseidon2Config ;
710use openvm_stark_backend:: {
811 config:: { StarkGenericConfig , Val } ,
@@ -18,7 +21,9 @@ use crate::{
1821 Arena , ChipInventoryError , ExecutorInventory , ExecutorInventoryError ,
1922 } ,
2023 system:: {
21- memory:: { merkle:: public_values:: PUBLIC_VALUES_AS , num_memory_airs, CHUNK } ,
24+ memory:: {
25+ merkle:: public_values:: PUBLIC_VALUES_AS , num_memory_airs, CHUNK , POINTER_MAX_BITS ,
26+ } ,
2227 SystemChipComplex ,
2328 } ,
2429} ;
@@ -105,6 +110,10 @@ where
105110
106111pub const OPENVM_DEFAULT_INIT_FILE_BASENAME : & str = "openvm_init" ;
107112pub const OPENVM_DEFAULT_INIT_FILE_NAME : & str = "openvm_init.rs" ;
113+ /// The minimum block size is 4, but RISC-V `lb` only requires alignment of 1 and `lh` only requires
114+ /// alignment of 2 because the instructions are implemented by doing an access of block size 4.
115+ const DEFAULT_U8_BLOCK_SIZE : usize = 4 ;
116+ const DEFAULT_NATIVE_BLOCK_SIZE : usize = 1 ;
108117
109118/// Trait for generating a init.rs file that contains a call to moduli_init!,
110119/// complex_init!, sw_init! with the supported moduli and curves.
@@ -132,18 +141,36 @@ pub trait InitFileGenerator {
132141 }
133142}
134143
144+ /// Each address space in guest memory may be configured with a different type `T` to represent a
145+ /// memory cell in the address space. On host, the address space will be mapped to linear host
146+ /// memory in bytes. The type `T` must be plain old data (POD) and be safely transmutable from a
147+ /// fixed size array of bytes. Moreover, each type `T` must be convertible to a field element `F`.
148+ ///
149+ /// We currently implement this trait on the enum [MemoryCellType], which includes all cell types
150+ /// that we expect to be used in the VM context.
151+ pub trait AddressSpaceHostLayout {
152+ /// Size in bytes of the memory cell type.
153+ fn size ( & self ) -> usize ;
154+
155+ /// # Safety
156+ /// - This function must only be called when `value` is guaranteed to be of size `self.size()`.
157+ /// - Alignment of `value` must be a multiple of the alignment of `F`.
158+ /// - The field type `F` must be plain old data.
159+ unsafe fn to_field < F : Field > ( & self , value : & [ u8 ] ) -> F ;
160+ }
161+
135162#[ derive( Debug , Serialize , Deserialize , Clone , new) ]
136163pub struct MemoryConfig {
137164 /// The maximum height of the address space. This means the trie has `addr_space_height` layers
138165 /// for searching the address space. The allowed address spaces are those in the range `[1,
139166 /// 1 + 2^addr_space_height)` where it starts from 1 to not allow address space 0 in memory.
140167 pub addr_space_height : usize ,
141- /// The number of cells in each address space. It is expected that the size of the list is
142- /// `1 << addr_space_height + 1` and the first element is 0, which means no address space.
143- pub addr_space_sizes : Vec < usize > ,
168+ /// It is expected that the size of the list is `(1 << addr_space_height) + 1` and the first
169+ /// element is 0, which means no address space.
170+ pub addr_spaces : Vec < AddressSpaceHostConfig > ,
144171 pub pointer_max_bits : usize ,
145- /// All timestamps must be in the range `[0, 2^clk_max_bits )`. Maximum allowed: 29.
146- pub clk_max_bits : usize ,
172+ /// All timestamps must be in the range `[0, 2^timestamp_max_bits )`. Maximum allowed: 29.
173+ pub timestamp_max_bits : usize ,
147174 /// Limb size used by the range checker
148175 pub decomp : usize ,
149176 /// Maximum N AccessAdapter AIR to support.
@@ -152,19 +179,46 @@ pub struct MemoryConfig {
152179
153180impl Default for MemoryConfig {
154181 fn default ( ) -> Self {
155- let mut addr_space_sizes = vec ! [ 0 ; ( 1 << 3 ) + ADDR_SPACE_OFFSET as usize ] ;
156- addr_space_sizes[ ADDR_SPACE_OFFSET as usize ..=NATIVE_AS as usize ] . fill ( 1 << 29 ) ;
157- addr_space_sizes[ PUBLIC_VALUES_AS as usize ] = DEFAULT_MAX_NUM_PUBLIC_VALUES ;
158- Self :: new ( 3 , addr_space_sizes, 29 , 29 , 17 , 32 )
182+ let mut addr_spaces =
183+ Self :: empty_address_space_configs ( ( 1 << 3 ) + ADDR_SPACE_OFFSET as usize ) ;
184+ const MAX_CELLS : usize = 1 << 29 ;
185+ addr_spaces[ RV32_REGISTER_AS as usize ] . num_cells = 32 * size_of :: < u32 > ( ) ;
186+ addr_spaces[ RV32_MEMORY_AS as usize ] . num_cells = MAX_CELLS ;
187+ addr_spaces[ PUBLIC_VALUES_AS as usize ] . num_cells = DEFAULT_MAX_NUM_PUBLIC_VALUES ;
188+ addr_spaces[ NATIVE_AS as usize ] . num_cells = MAX_CELLS ;
189+ Self :: new ( 3 , addr_spaces, POINTER_MAX_BITS , 29 , 17 , 32 )
159190 }
160191}
161192
162193impl MemoryConfig {
194+ pub fn empty_address_space_configs ( num_addr_spaces : usize ) -> Vec < AddressSpaceHostConfig > {
195+ // All except address spaces 0..4 default to native 32-bit field.
196+ // By default only address spaces 1..=4 have non-empty cell counts.
197+ let mut addr_spaces = vec ! [
198+ AddressSpaceHostConfig :: new(
199+ 0 ,
200+ DEFAULT_NATIVE_BLOCK_SIZE ,
201+ MemoryCellType :: native32( )
202+ ) ;
203+ num_addr_spaces
204+ ] ;
205+ addr_spaces[ RV32_IMM_AS as usize ] = AddressSpaceHostConfig :: new ( 0 , 1 , MemoryCellType :: Null ) ;
206+ addr_spaces[ RV32_REGISTER_AS as usize ] =
207+ AddressSpaceHostConfig :: new ( 0 , DEFAULT_U8_BLOCK_SIZE , MemoryCellType :: U8 ) ;
208+ addr_spaces[ RV32_MEMORY_AS as usize ] =
209+ AddressSpaceHostConfig :: new ( 0 , DEFAULT_U8_BLOCK_SIZE , MemoryCellType :: U8 ) ;
210+ addr_spaces[ PUBLIC_VALUES_AS as usize ] =
211+ AddressSpaceHostConfig :: new ( 0 , DEFAULT_U8_BLOCK_SIZE , MemoryCellType :: U8 ) ;
212+
213+ addr_spaces
214+ }
215+
163216 /// Config for aggregation usage with only native address space.
164217 pub fn aggregation ( ) -> Self {
165- let mut addr_space_sizes = vec ! [ 0 ; ( 1 << 3 ) + ADDR_SPACE_OFFSET as usize ] ;
166- addr_space_sizes[ NATIVE_AS as usize ] = 1 << 29 ;
167- Self :: new ( 3 , addr_space_sizes, 29 , 29 , 17 , 8 )
218+ let mut addr_spaces =
219+ Self :: empty_address_space_configs ( ( 1 << 3 ) + ADDR_SPACE_OFFSET as usize ) ;
220+ addr_spaces[ NATIVE_AS as usize ] . num_cells = 1 << 29 ;
221+ Self :: new ( 3 , addr_spaces, POINTER_MAX_BITS , 29 , 17 , 8 )
168222 }
169223}
170224
@@ -209,10 +263,10 @@ impl SystemConfig {
209263 num_public_values : usize ,
210264 ) -> Self {
211265 assert ! (
212- memory_config. clk_max_bits <= 29 ,
266+ memory_config. timestamp_max_bits <= 29 ,
213267 "Timestamp max bits must be <= 29 for LessThan to work in 31-bit field"
214268 ) ;
215- memory_config. addr_space_sizes [ PUBLIC_VALUES_AS as usize ] = num_public_values;
269+ memory_config. addr_spaces [ PUBLIC_VALUES_AS as usize ] . num_cells = num_public_values;
216270 Self {
217271 max_constraint_degree,
218272 continuation_enabled : false ,
@@ -243,7 +297,7 @@ impl SystemConfig {
243297
244298 pub fn with_public_values ( mut self , num_public_values : usize ) -> Self {
245299 self . num_public_values = num_public_values;
246- self . memory_config . addr_space_sizes [ PUBLIC_VALUES_AS as usize ] = num_public_values;
300+ self . memory_config . addr_spaces [ PUBLIC_VALUES_AS as usize ] . num_cells = num_public_values;
247301 self
248302 }
249303
@@ -316,3 +370,67 @@ impl AsMut<SystemConfig> for SystemConfig {
316370
317371// Default implementation uses no init file
318372impl InitFileGenerator for SystemConfig { }
373+
374+ #[ derive( Debug , Serialize , Deserialize , Clone , Copy , new) ]
375+ pub struct AddressSpaceHostConfig {
376+ /// The number of cells in each address space.
377+ pub num_cells : usize ,
378+ /// Minimum block size for memory accesses supported. This is a property of the address space
379+ /// that is determined by the ISA.
380+ ///
381+ /// **Note**: Block size is in terms of memory cells.
382+ pub min_block_size : usize ,
383+ pub layout : MemoryCellType ,
384+ }
385+
386+ pub ( crate ) const MAX_CELL_BYTE_SIZE : usize = 8 ;
387+
388+ #[ derive( Debug , Serialize , Deserialize , Clone , Copy , PartialEq , Eq ) ]
389+ pub enum MemoryCellType {
390+ Null ,
391+ U8 ,
392+ U16 ,
393+ /// Represented in little-endian format.
394+ U32 ,
395+ /// `size` is the size in bytes of the native field type. This should not exceed 8.
396+ Native {
397+ size : u8 ,
398+ } ,
399+ }
400+
401+ impl MemoryCellType {
402+ pub fn native32 ( ) -> Self {
403+ Self :: Native {
404+ size : size_of :: < u32 > ( ) as u8 ,
405+ }
406+ }
407+ }
408+
409+ impl AddressSpaceHostLayout for MemoryCellType {
410+ fn size ( & self ) -> usize {
411+ match self {
412+ Self :: Null => 1 , // to avoid divide by zero
413+ Self :: U8 => size_of :: < u8 > ( ) ,
414+ Self :: U16 => size_of :: < u16 > ( ) ,
415+ Self :: U32 => size_of :: < u32 > ( ) ,
416+ Self :: Native { size } => * size as usize ,
417+ }
418+ }
419+
420+ /// # Safety
421+ /// - This function must only be called when `value` is guaranteed to be of size `self.size()`.
422+ /// - Alignment of `value` must be a multiple of the alignment of `F`.
423+ /// - The field type `F` must be plain old data.
424+ ///
425+ /// # Panics
426+ /// If the value is of integer type and overflows the field.
427+ unsafe fn to_field < F : Field > ( & self , value : & [ u8 ] ) -> F {
428+ match self {
429+ Self :: Null => unreachable ! ( ) ,
430+ Self :: U8 => F :: from_canonical_u8 ( * value. get_unchecked ( 0 ) ) ,
431+ Self :: U16 => F :: from_canonical_u16 ( core:: ptr:: read ( value. as_ptr ( ) as * const u16 ) ) ,
432+ Self :: U32 => F :: from_canonical_u32 ( core:: ptr:: read ( value. as_ptr ( ) as * const u32 ) ) ,
433+ Self :: Native { .. } => core:: ptr:: read ( value. as_ptr ( ) as * const F ) ,
434+ }
435+ }
436+ }
0 commit comments