@@ -2,7 +2,10 @@ use std::{fs::File, io::Write, path::Path};
2
2
3
3
use derive_new:: new;
4
4
use 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
+ } ;
6
9
use openvm_poseidon2_air:: Poseidon2Config ;
7
10
use openvm_stark_backend:: {
8
11
config:: { StarkGenericConfig , Val } ,
@@ -18,7 +21,9 @@ use crate::{
18
21
Arena , ChipInventoryError , ExecutorInventory , ExecutorInventoryError ,
19
22
} ,
20
23
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
+ } ,
22
27
SystemChipComplex ,
23
28
} ,
24
29
} ;
@@ -105,6 +110,10 @@ where
105
110
106
111
pub const OPENVM_DEFAULT_INIT_FILE_BASENAME : & str = "openvm_init" ;
107
112
pub 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 ;
108
117
109
118
/// Trait for generating a init.rs file that contains a call to moduli_init!,
110
119
/// complex_init!, sw_init! with the supported moduli and curves.
@@ -132,18 +141,36 @@ pub trait InitFileGenerator {
132
141
}
133
142
}
134
143
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
+
135
162
#[ derive( Debug , Serialize , Deserialize , Clone , new) ]
136
163
pub struct MemoryConfig {
137
164
/// The maximum height of the address space. This means the trie has `addr_space_height` layers
138
165
/// for searching the address space. The allowed address spaces are those in the range `[1,
139
166
/// 1 + 2^addr_space_height)` where it starts from 1 to not allow address space 0 in memory.
140
167
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 > ,
144
171
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 ,
147
174
/// Limb size used by the range checker
148
175
pub decomp : usize ,
149
176
/// Maximum N AccessAdapter AIR to support.
@@ -152,19 +179,46 @@ pub struct MemoryConfig {
152
179
153
180
impl Default for MemoryConfig {
154
181
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 )
159
190
}
160
191
}
161
192
162
193
impl 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
+
163
216
/// Config for aggregation usage with only native address space.
164
217
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 )
168
222
}
169
223
}
170
224
@@ -209,10 +263,10 @@ impl SystemConfig {
209
263
num_public_values : usize ,
210
264
) -> Self {
211
265
assert ! (
212
- memory_config. clk_max_bits <= 29 ,
266
+ memory_config. timestamp_max_bits <= 29 ,
213
267
"Timestamp max bits must be <= 29 for LessThan to work in 31-bit field"
214
268
) ;
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;
216
270
Self {
217
271
max_constraint_degree,
218
272
continuation_enabled : false ,
@@ -243,7 +297,7 @@ impl SystemConfig {
243
297
244
298
pub fn with_public_values ( mut self , num_public_values : usize ) -> Self {
245
299
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;
247
301
self
248
302
}
249
303
@@ -316,3 +370,67 @@ impl AsMut<SystemConfig> for SystemConfig {
316
370
317
371
// Default implementation uses no init file
318
372
impl 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