1use std::{
2 fs::File,
3 io::{self, Write},
4 path::Path,
5};
6
7use derive_new::new;
8use getset::{Setters, WithSetters};
9use openvm_instructions::{
10 riscv::{RV32_IMM_AS, RV32_MEMORY_AS, RV32_REGISTER_AS},
11 NATIVE_AS,
12};
13use openvm_poseidon2_air::Poseidon2Config;
14use openvm_stark_backend::{
15 config::{StarkGenericConfig, Val},
16 engine::StarkEngine,
17 p3_field::Field,
18 p3_util::log2_strict_usize,
19};
20use serde::{de::DeserializeOwned, Deserialize, Serialize};
21
22use super::{AnyEnum, VmChipComplex, PUBLIC_VALUES_AIR_ID};
23use crate::{
24 arch::{
25 execution_mode::metered::segment_ctx::SegmentationLimits, AirInventory, AirInventoryError,
26 Arena, ChipInventoryError, ExecutorInventory, ExecutorInventoryError,
27 },
28 system::{
29 memory::{
30 merkle::public_values::PUBLIC_VALUES_AS, num_memory_airs, CHUNK, POINTER_MAX_BITS,
31 },
32 SystemChipComplex,
33 },
34};
35
36const DEFAULT_POSEIDON2_MAX_CONSTRAINT_DEGREE: usize = 3;
39pub const DEFAULT_MAX_NUM_PUBLIC_VALUES: usize = 32;
40pub const POSEIDON2_WIDTH: usize = 16;
42pub const ADDR_SPACE_OFFSET: u32 = 1;
44pub fn vm_poseidon2_config<F: Field>() -> Poseidon2Config<F> {
46 Poseidon2Config::default()
47}
48
49pub trait VmConfig<SC>:
61 Clone
62 + Serialize
63 + DeserializeOwned
64 + InitFileGenerator
65 + VmExecutionConfig<Val<SC>>
66 + VmCircuitConfig<SC>
67 + AsRef<SystemConfig>
68 + AsMut<SystemConfig>
69where
70 SC: StarkGenericConfig,
71{
72}
73
74pub trait VmExecutionConfig<F> {
75 type Executor: AnyEnum + Send + Sync;
76
77 fn create_executors(&self)
78 -> Result<ExecutorInventory<Self::Executor>, ExecutorInventoryError>;
79}
80
81pub trait VmCircuitConfig<SC: StarkGenericConfig> {
82 fn create_airs(&self) -> Result<AirInventory<SC>, AirInventoryError>;
83}
84
85pub trait VmBuilder<E: StarkEngine>: Sized {
88 type VmConfig: VmConfig<E::SC>;
89 type RecordArena: Arena;
90 type SystemChipInventory: SystemChipComplex<Self::RecordArena, E::PB>;
91
92 #[allow(clippy::type_complexity)]
95 fn create_chip_complex(
96 &self,
97 config: &Self::VmConfig,
98 circuit: AirInventory<E::SC>,
99 ) -> Result<
100 VmChipComplex<E::SC, Self::RecordArena, E::PB, Self::SystemChipInventory>,
101 ChipInventoryError,
102 >;
103}
104
105impl<SC, VC> VmConfig<SC> for VC
106where
107 SC: StarkGenericConfig,
108 VC: Clone
109 + Serialize
110 + DeserializeOwned
111 + InitFileGenerator
112 + VmExecutionConfig<Val<SC>>
113 + VmCircuitConfig<SC>
114 + AsRef<SystemConfig>
115 + AsMut<SystemConfig>,
116{
117}
118
119pub const OPENVM_DEFAULT_INIT_FILE_BASENAME: &str = "openvm_init";
120pub const OPENVM_DEFAULT_INIT_FILE_NAME: &str = "openvm_init.rs";
121const DEFAULT_U8_BLOCK_SIZE: usize = 4;
124const DEFAULT_NATIVE_BLOCK_SIZE: usize = 1;
125
126pub trait InitFileGenerator {
130 fn generate_init_file_contents(&self) -> Option<String> {
132 None
133 }
134
135 fn write_to_init_file(
138 &self,
139 manifest_dir: &Path,
140 init_file_name: Option<&str>,
141 ) -> io::Result<()> {
142 if let Some(contents) = self.generate_init_file_contents() {
143 let dest_path = Path::new(manifest_dir)
144 .join(init_file_name.unwrap_or(OPENVM_DEFAULT_INIT_FILE_NAME));
145 let mut f = File::create(&dest_path)?;
146 write!(f, "{}", contents)?;
147 }
148 Ok(())
149 }
150}
151
152pub trait AddressSpaceHostLayout {
160 fn size(&self) -> usize;
162
163 unsafe fn to_field<F: Field>(&self, value: &[u8]) -> F;
168}
169
170#[derive(Debug, Serialize, Deserialize, Clone, new)]
171pub struct MemoryConfig {
172 pub addr_space_height: usize,
176 pub addr_spaces: Vec<AddressSpaceHostConfig>,
179 pub pointer_max_bits: usize,
180 pub timestamp_max_bits: usize,
182 pub decomp: usize,
184 pub max_access_adapter_n: usize,
186}
187
188impl Default for MemoryConfig {
189 fn default() -> Self {
190 let mut addr_spaces =
191 Self::empty_address_space_configs((1 << 3) + ADDR_SPACE_OFFSET as usize);
192 const MAX_CELLS: usize = 1 << 29;
193 addr_spaces[RV32_REGISTER_AS as usize].num_cells = 32 * size_of::<u32>();
194 addr_spaces[RV32_MEMORY_AS as usize].num_cells = MAX_CELLS;
195 addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = DEFAULT_MAX_NUM_PUBLIC_VALUES;
196 addr_spaces[NATIVE_AS as usize].num_cells = MAX_CELLS;
197 Self::new(3, addr_spaces, POINTER_MAX_BITS, 29, 17, 32)
198 }
199}
200
201impl MemoryConfig {
202 pub fn empty_address_space_configs(num_addr_spaces: usize) -> Vec<AddressSpaceHostConfig> {
203 let mut addr_spaces = vec![
206 AddressSpaceHostConfig::new(
207 0,
208 DEFAULT_NATIVE_BLOCK_SIZE,
209 MemoryCellType::native32()
210 );
211 num_addr_spaces
212 ];
213 addr_spaces[RV32_IMM_AS as usize] = AddressSpaceHostConfig::new(0, 1, MemoryCellType::Null);
214 addr_spaces[RV32_REGISTER_AS as usize] =
215 AddressSpaceHostConfig::new(0, DEFAULT_U8_BLOCK_SIZE, MemoryCellType::U8);
216
217 #[cfg(feature = "legacy-v1-3-mem-align")]
218 {
219 addr_spaces[RV32_MEMORY_AS as usize] =
220 AddressSpaceHostConfig::new(0, 1, MemoryCellType::U8);
221 }
222 #[cfg(not(feature = "legacy-v1-3-mem-align"))]
223 {
224 addr_spaces[RV32_MEMORY_AS as usize] =
225 AddressSpaceHostConfig::new(0, DEFAULT_U8_BLOCK_SIZE, MemoryCellType::U8);
226 }
227
228 addr_spaces[PUBLIC_VALUES_AS as usize] =
229 AddressSpaceHostConfig::new(0, DEFAULT_U8_BLOCK_SIZE, MemoryCellType::U8);
230
231 addr_spaces
232 }
233
234 pub fn aggregation() -> Self {
236 let mut addr_spaces =
237 Self::empty_address_space_configs((1 << 3) + ADDR_SPACE_OFFSET as usize);
238 addr_spaces[NATIVE_AS as usize].num_cells = 1 << 29;
239 Self::new(3, addr_spaces, POINTER_MAX_BITS, 29, 17, 8)
240 }
241
242 pub fn min_block_size_bits(&self) -> Vec<u8> {
243 self.addr_spaces
244 .iter()
245 .map(|addr_sp| log2_strict_usize(addr_sp.min_block_size) as u8)
246 .collect()
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, Setters, WithSetters)]
253pub struct SystemConfig {
254 #[getset(set_with = "pub")]
256 pub max_constraint_degree: usize,
257 pub continuation_enabled: bool,
263 pub memory_config: MemoryConfig,
265 pub num_public_values: usize,
273 pub profiling: bool,
276 #[serde(skip, default = "SegmentationLimits::default")]
280 #[getset(set = "pub")]
281 pub segmentation_limits: SegmentationLimits,
282}
283
284impl SystemConfig {
285 pub fn new(
286 max_constraint_degree: usize,
287 mut memory_config: MemoryConfig,
288 num_public_values: usize,
289 ) -> Self {
290 assert!(
291 memory_config.timestamp_max_bits <= 29,
292 "Timestamp max bits must be <= 29 for LessThan to work in 31-bit field"
293 );
294 memory_config.addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = num_public_values;
295 Self {
296 max_constraint_degree,
297 continuation_enabled: true,
298 memory_config,
299 num_public_values,
300 profiling: false,
301 segmentation_limits: SegmentationLimits::default(),
302 }
303 }
304
305 pub fn default_from_memory(memory_config: MemoryConfig) -> Self {
306 Self::new(
307 DEFAULT_POSEIDON2_MAX_CONSTRAINT_DEGREE,
308 memory_config,
309 DEFAULT_MAX_NUM_PUBLIC_VALUES,
310 )
311 }
312
313 pub fn with_continuations(mut self) -> Self {
314 self.continuation_enabled = true;
315 self
316 }
317
318 pub fn without_continuations(mut self) -> Self {
319 self.continuation_enabled = false;
320 self
321 }
322
323 pub fn with_public_values(mut self, num_public_values: usize) -> Self {
324 self.num_public_values = num_public_values;
325 self.memory_config.addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = num_public_values;
326 self
327 }
328
329 pub fn with_max_segment_len(mut self, max_segment_len: usize) -> Self {
330 self.segmentation_limits.max_trace_height = max_segment_len as u32;
331 self
332 }
333
334 pub fn with_profiling(mut self) -> Self {
335 self.profiling = true;
336 self
337 }
338
339 pub fn without_profiling(mut self) -> Self {
340 self.profiling = false;
341 self
342 }
343
344 pub fn has_public_values_chip(&self) -> bool {
345 !self.continuation_enabled && self.num_public_values > 0
346 }
347
348 pub fn memory_boundary_air_id(&self) -> usize {
350 PUBLIC_VALUES_AIR_ID + usize::from(self.has_public_values_chip())
351 }
352
353 pub fn memory_merkle_air_id(&self) -> Option<usize> {
355 let boundary_idx = self.memory_boundary_air_id();
356 if self.continuation_enabled {
357 Some(boundary_idx + 1)
358 } else {
359 None
360 }
361 }
362
363 pub fn access_adapter_air_id_offset(&self) -> usize {
365 let boundary_idx = self.memory_boundary_air_id();
366 boundary_idx + 1 + usize::from(self.continuation_enabled)
368 }
369
370 pub fn num_airs(&self) -> usize {
373 self.memory_boundary_air_id()
374 + num_memory_airs(
375 self.continuation_enabled,
376 self.memory_config.max_access_adapter_n,
377 )
378 }
379
380 pub fn initial_block_size(&self) -> usize {
381 match self.continuation_enabled {
382 true => CHUNK,
383 false => 1,
384 }
385 }
386}
387
388impl Default for SystemConfig {
389 fn default() -> Self {
390 Self::default_from_memory(MemoryConfig::default())
391 }
392}
393
394impl AsRef<SystemConfig> for SystemConfig {
395 fn as_ref(&self) -> &SystemConfig {
396 self
397 }
398}
399
400impl AsMut<SystemConfig> for SystemConfig {
401 fn as_mut(&mut self) -> &mut SystemConfig {
402 self
403 }
404}
405
406impl InitFileGenerator for SystemConfig {}
408
409#[derive(Debug, Serialize, Deserialize, Clone, Copy, new)]
410pub struct AddressSpaceHostConfig {
411 pub num_cells: usize,
414 pub min_block_size: usize,
419 pub layout: MemoryCellType,
420}
421
422impl AddressSpaceHostConfig {
423 pub fn size(&self) -> usize {
425 self.num_cells * self.layout.size()
426 }
427}
428
429pub(crate) const MAX_CELL_BYTE_SIZE: usize = 8;
430
431#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
432pub enum MemoryCellType {
433 Null,
434 U8,
435 U16,
436 U32,
438 Native {
440 size: u8,
441 },
442}
443
444impl MemoryCellType {
445 pub fn native32() -> Self {
446 Self::Native {
447 size: size_of::<u32>() as u8,
448 }
449 }
450}
451
452impl AddressSpaceHostLayout for MemoryCellType {
453 fn size(&self) -> usize {
454 match self {
455 Self::Null => 1, Self::U8 => size_of::<u8>(),
457 Self::U16 => size_of::<u16>(),
458 Self::U32 => size_of::<u32>(),
459 Self::Native { size } => *size as usize,
460 }
461 }
462
463 unsafe fn to_field<F: Field>(&self, value: &[u8]) -> F {
471 match self {
472 Self::Null => unreachable!(),
473 Self::U8 => F::from_canonical_u8(*value.get_unchecked(0)),
474 Self::U16 => F::from_canonical_u16(core::ptr::read(value.as_ptr() as *const u16)),
475 Self::U32 => F::from_canonical_u32(core::ptr::read(value.as_ptr() as *const u32)),
476 Self::Native { .. } => core::ptr::read(value.as_ptr() as *const F),
477 }
478 }
479}