use std::{
fs::File,
io::{self, Write},
path::Path,
};
use derive_new::new;
use getset::{Setters, WithSetters};
use openvm_instructions::{
riscv::{RV32_IMM_AS, RV32_MEMORY_AS, RV32_REGISTER_AS},
DEFERRAL_AS,
};
use openvm_poseidon2_air::Poseidon2Config;
use openvm_stark_backend::{
p3_field::Field, EngineDeviceCtx, StarkEngine, StarkProtocolConfig, Val,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use super::{AnyEnum, VmChipComplex, BOUNDARY_AIR_ID, CONNECTOR_AIR_ID, PROGRAM_AIR_ID};
use crate::{
arch::{
execution_mode::metered::segment_ctx::DEFAULT_MAX_MEMORY, AirInventory, AirInventoryError,
Arena, ChipInventoryError, ExecutorInventory, ExecutorInventoryError,
},
system::{
memory::{merkle::public_values::PUBLIC_VALUES_AS, num_memory_airs, POINTER_MAX_BITS},
SystemChipComplex,
},
};
const DEFAULT_POSEIDON2_MAX_CONSTRAINT_DEGREE: usize = 3;
pub const DEFAULT_MAX_NUM_PUBLIC_VALUES: usize = 32;
pub const DEFAULT_DEFERRAL_ADDR_SPACE_CELLS: usize = 1 << 14;
pub const POSEIDON2_WIDTH: usize = 16;
pub const ADDR_SPACE_OFFSET: u32 = 1;
fn default_segmentation_max_memory() -> usize {
DEFAULT_MAX_MEMORY
}
pub fn vm_poseidon2_config<F: Field>() -> Poseidon2Config<F> {
Poseidon2Config::default()
}
pub trait VmConfig<SC>:
Clone
+ Serialize
+ DeserializeOwned
+ InitFileGenerator
+ VmExecutionConfig<Val<SC>>
+ VmCircuitConfig<SC>
+ AsRef<SystemConfig>
+ AsMut<SystemConfig>
where
SC: StarkProtocolConfig,
{
}
pub trait VmExecutionConfig<F> {
type Executor: AnyEnum;
fn create_executors(&self)
-> Result<ExecutorInventory<Self::Executor>, ExecutorInventoryError>;
}
pub trait VmCircuitConfig<SC: StarkProtocolConfig> {
fn create_airs(&self) -> Result<AirInventory<SC>, AirInventoryError>;
}
pub trait VmBuilder<E: StarkEngine>: Sized {
type VmConfig: VmConfig<E::SC>;
type RecordArena: Arena;
type SystemChipInventory: SystemChipComplex<Self::RecordArena, E::PB>;
#[allow(clippy::type_complexity)]
fn create_chip_complex(
&self,
config: &Self::VmConfig,
circuit: AirInventory<E::SC>,
device_ctx: &EngineDeviceCtx<E>,
) -> Result<
VmChipComplex<E::SC, Self::RecordArena, E::PB, Self::SystemChipInventory>,
ChipInventoryError,
>;
}
impl<SC, VC> VmConfig<SC> for VC
where
SC: StarkProtocolConfig,
VC: Clone
+ Serialize
+ DeserializeOwned
+ InitFileGenerator
+ VmExecutionConfig<Val<SC>>
+ VmCircuitConfig<SC>
+ AsRef<SystemConfig>
+ AsMut<SystemConfig>,
{
}
pub const OPENVM_DEFAULT_INIT_FILE_BASENAME: &str = "openvm_init";
pub const OPENVM_DEFAULT_INIT_FILE_NAME: &str = "openvm_init.rs";
pub const DEFAULT_BLOCK_SIZE: usize = 4;
pub trait InitFileGenerator {
fn generate_init_file_contents(&self) -> Option<String> {
None
}
fn write_to_init_file(
&self,
manifest_dir: &Path,
init_file_name: Option<&str>,
) -> io::Result<()> {
if let Some(contents) = self.generate_init_file_contents() {
let dest_path = Path::new(manifest_dir)
.join(init_file_name.unwrap_or(OPENVM_DEFAULT_INIT_FILE_NAME));
let mut f = File::create(&dest_path)?;
write!(f, "{contents}")?;
}
Ok(())
}
}
pub trait AddressSpaceHostLayout {
fn size(&self) -> usize;
unsafe fn to_field<F: Field>(&self, value: &[u8]) -> F;
}
#[derive(Debug, Serialize, Deserialize, Clone, new)]
pub struct MemoryConfig {
pub addr_space_height: usize,
pub addr_spaces: Vec<AddressSpaceHostConfig>,
pub pointer_max_bits: usize,
pub timestamp_max_bits: usize,
pub decomp: usize,
}
impl Default for MemoryConfig {
fn default() -> Self {
let mut addr_spaces =
Self::empty_address_space_configs((1 << 3) + ADDR_SPACE_OFFSET as usize);
const MAX_CELLS: usize = 1 << 29;
addr_spaces[RV32_REGISTER_AS as usize].num_cells = 32 * size_of::<u32>();
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[DEFERRAL_AS as usize].num_cells = DEFAULT_DEFERRAL_ADDR_SPACE_CELLS;
Self::new(3, addr_spaces, POINTER_MAX_BITS, 29, 17)
}
}
impl MemoryConfig {
pub fn empty_address_space_configs(num_addr_spaces: usize) -> Vec<AddressSpaceHostConfig> {
let mut addr_spaces =
vec![AddressSpaceHostConfig::new(0, MemoryCellType::field32()); num_addr_spaces];
addr_spaces[RV32_IMM_AS as usize] = AddressSpaceHostConfig::new(0, MemoryCellType::Null);
addr_spaces[RV32_REGISTER_AS as usize] = AddressSpaceHostConfig::new(0, MemoryCellType::U8);
addr_spaces[RV32_MEMORY_AS as usize] = AddressSpaceHostConfig::new(0, MemoryCellType::U8);
addr_spaces[PUBLIC_VALUES_AS as usize] = AddressSpaceHostConfig::new(0, MemoryCellType::U8);
addr_spaces
}
pub fn aggregation() -> Self {
let mut addr_spaces =
Self::empty_address_space_configs((1 << 3) + ADDR_SPACE_OFFSET as usize);
addr_spaces[openvm_instructions::DEFERRAL_AS as usize].num_cells = 1 << 29;
Self::new(3, addr_spaces, POINTER_MAX_BITS, 29, 17)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Setters, WithSetters)]
pub struct SystemConfig {
#[getset(set_with = "pub")]
pub max_constraint_degree: usize,
pub memory_config: MemoryConfig,
pub num_public_values: usize,
#[serde(skip, default = "default_segmentation_max_memory")]
#[getset(set = "pub")]
pub segmentation_max_memory: usize,
}
impl SystemConfig {
pub fn new(
max_constraint_degree: usize,
mut memory_config: MemoryConfig,
num_public_values: usize,
) -> Self {
assert!(
memory_config.timestamp_max_bits <= 29,
"Timestamp max bits must be <= 29 for LessThan to work in 31-bit field"
);
memory_config.addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = num_public_values;
Self {
max_constraint_degree,
memory_config,
num_public_values,
segmentation_max_memory: DEFAULT_MAX_MEMORY,
}
}
pub fn default_from_memory(memory_config: MemoryConfig) -> Self {
Self::new(
DEFAULT_POSEIDON2_MAX_CONSTRAINT_DEGREE,
memory_config,
DEFAULT_MAX_NUM_PUBLIC_VALUES,
)
}
pub fn with_public_values(mut self, num_public_values: usize) -> Self {
self.num_public_values = num_public_values;
self.memory_config.addr_spaces[PUBLIC_VALUES_AS as usize].num_cells = num_public_values;
self
}
pub fn memory_boundary_air_id(&self) -> usize {
BOUNDARY_AIR_ID
}
pub fn memory_merkle_air_id(&self) -> usize {
self.memory_boundary_air_id() + 1
}
pub fn is_required_air_id(&self, air_id: usize) -> bool {
air_id == PROGRAM_AIR_ID
|| air_id == CONNECTOR_AIR_ID
|| air_id == self.memory_boundary_air_id()
|| air_id == self.memory_merkle_air_id()
}
pub fn num_airs(&self) -> usize {
self.memory_boundary_air_id() + num_memory_airs()
}
}
impl Default for SystemConfig {
fn default() -> Self {
Self::default_from_memory(MemoryConfig::default())
}
}
impl AsRef<SystemConfig> for SystemConfig {
fn as_ref(&self) -> &SystemConfig {
self
}
}
impl AsMut<SystemConfig> for SystemConfig {
fn as_mut(&mut self) -> &mut SystemConfig {
self
}
}
impl InitFileGenerator for SystemConfig {}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, new)]
pub struct AddressSpaceHostConfig {
pub num_cells: usize,
pub layout: MemoryCellType,
}
impl AddressSpaceHostConfig {
pub fn size(&self) -> usize {
self.num_cells * self.layout.size()
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum MemoryCellType {
Null,
U8,
U16,
U32,
F {
size: u8,
},
}
impl MemoryCellType {
pub fn field32() -> Self {
Self::F {
size: size_of::<u32>() as u8,
}
}
}
impl AddressSpaceHostLayout for MemoryCellType {
fn size(&self) -> usize {
match self {
Self::Null => 1, Self::U8 => size_of::<u8>(),
Self::U16 => size_of::<u16>(),
Self::U32 => size_of::<u32>(),
Self::F { size } => *size as usize,
}
}
unsafe fn to_field<F: Field>(&self, value: &[u8]) -> F {
match self {
Self::Null => unreachable!(),
Self::U8 => F::from_u8(*value.get_unchecked(0)),
Self::U16 => F::from_u16(core::ptr::read(value.as_ptr() as *const u16)),
Self::U32 => F::from_u32(core::ptr::read(value.as_ptr() as *const u32)),
Self::F { .. } => core::ptr::read(value.as_ptr() as *const F),
}
}
}