use getset::{Getters, Setters, WithSetters};
use itertools::Itertools;
use openvm_instructions::riscv::{RV32_IMM_AS, RV32_REGISTER_AS};
use openvm_stark_backend::memory_metering::ProvingMemoryConfig;
use super::{
memory_ctx::MemoryCtx,
segment_ctx::{Segment, SegmentationCtx, SegmentationLimits},
};
use crate::{
arch::{
execution_mode::{ExecutionCtxTrait, MeteredExecutionCtxTrait},
SystemConfig, VmExecState, BOUNDARY_AIR_ID, MERKLE_AIR_ID,
},
system::memory::online::GuestMemory,
};
pub const DEFAULT_PAGE_BITS: usize = 6;
#[derive(Clone, Debug, Getters, Setters, WithSetters)]
pub struct MeteredCtx<const PAGE_BITS: usize = DEFAULT_PAGE_BITS> {
pub trace_heights: Vec<u32>,
pub is_trace_height_constant: Vec<bool>,
pub memory_ctx: MemoryCtx<PAGE_BITS>,
pub segmentation_ctx: SegmentationCtx,
#[getset(get = "pub", set = "pub", set_with = "pub")]
suspend_on_segment: bool,
}
pub struct MeteredCtxInputs<'a> {
pub constant_trace_heights: &'a [Option<usize>],
pub air_names: &'a [String],
pub widths: &'a [usize],
pub interactions: &'a [usize],
pub need_rot: &'a [bool],
pub segmentation_limits: SegmentationLimits,
}
impl<const PAGE_BITS: usize> MeteredCtx<PAGE_BITS> {
pub fn new(
inputs: MeteredCtxInputs<'_>,
config: &SystemConfig,
memory_config: ProvingMemoryConfig,
) -> Self {
let (trace_heights, is_trace_height_constant): (Vec<u32>, Vec<bool>) = inputs
.constant_trace_heights
.iter()
.map(|&constant_height| {
if let Some(height) = constant_height {
(height as u32, true)
} else {
(0, false)
}
})
.unzip();
let segmentation_ctx = SegmentationCtx::new(
inputs.air_names.to_vec(),
inputs.widths.to_vec(),
inputs.interactions.to_vec(),
inputs.need_rot.to_vec(),
inputs.segmentation_limits,
memory_config,
);
let memory_ctx = MemoryCtx::new(config, segmentation_ctx.segment_check_insns());
let air_names = segmentation_ctx.air_names();
debug_assert!(
air_names[BOUNDARY_AIR_ID].contains("Boundary"),
"air_name={}",
air_names[BOUNDARY_AIR_ID]
);
debug_assert!(
air_names[MERKLE_AIR_ID].contains("Merkle"),
"air_name={}",
air_names[MERKLE_AIR_ID]
);
let mut ctx = Self {
trace_heights,
is_trace_height_constant,
memory_ctx,
segmentation_ctx,
suspend_on_segment: false,
};
ctx.memory_ctx.add_register_merkle_heights();
ctx.memory_ctx
.lazy_update_boundary_heights(&mut ctx.trace_heights);
ctx
}
pub fn with_max_memory(mut self, max_memory: usize) -> Self {
self.segmentation_ctx.set_max_memory(max_memory);
self
}
pub fn segments(&self) -> &[Segment] {
&self.segmentation_ctx.segments
}
pub fn into_segments(self) -> Vec<Segment> {
self.segmentation_ctx.segments
}
#[inline(always)]
pub fn check_and_segment(&mut self) -> bool {
if self.segmentation_ctx.instrets_until_check > 0 {
return false;
}
let segment_check_insns = self.segmentation_ctx.segment_check_insns();
self.segmentation_ctx.instrets_until_check = segment_check_insns;
self.segmentation_ctx.instret += segment_check_insns;
self.memory_ctx
.lazy_update_boundary_heights(&mut self.trace_heights);
let did_segment = self.segmentation_ctx.check_and_segment(
self.segmentation_ctx.instret,
&mut self.trace_heights,
&self.is_trace_height_constant,
);
if did_segment {
self.segmentation_ctx
.initialize_segment(&mut self.trace_heights, &self.is_trace_height_constant);
self.memory_ctx.initialize_segment(&mut self.trace_heights);
if self.segmentation_ctx.should_segment(
self.segmentation_ctx.instret,
&self.trace_heights,
&self.is_trace_height_constant,
) {
let trace_heights_str = self
.trace_heights
.iter()
.zip(self.segmentation_ctx.air_names().iter())
.filter(|(&height, _)| height > 0)
.map(|(&height, name)| format!(" {name} = {height}"))
.collect::<Vec<_>>()
.join("\n");
tracing::warn!(
"Segment initialized with heights that exceed limits\n\
instret={}\n\
trace_heights=[\n{}\n]",
self.segmentation_ctx.instret,
trace_heights_str
);
}
}
self.segmentation_ctx
.update_checkpoint(self.segmentation_ctx.instret, &self.trace_heights);
self.memory_ctx.update_checkpoint();
did_segment
}
#[allow(dead_code)]
pub fn print_segment(&self) {
println!("{}", "-".repeat(80));
println!("Segment {}", self.segmentation_ctx.segments.len() - 1);
println!("{}", "-".repeat(80));
println!("{:>10} {:>10} {:<30}", "Width", "Height", "Air Name");
println!("{}", "-".repeat(80));
for ((&width, &height), air_name) in self
.segmentation_ctx
.widths()
.iter()
.zip_eq(self.trace_heights.iter())
.zip_eq(self.segmentation_ctx.air_names().iter())
{
println!("{:>10} {:>10} {:<30}", width, height, air_name.as_str());
}
}
}
impl<const PAGE_BITS: usize> ExecutionCtxTrait for MeteredCtx<PAGE_BITS> {
#[inline(always)]
fn on_memory_operation(&mut self, address_space: u32, ptr: u32, size: u32) {
debug_assert!(
address_space != RV32_IMM_AS,
"address space must not be immediate"
);
debug_assert!(size > 0, "size must be greater than 0, got {size}");
debug_assert!(
size.is_power_of_two(),
"size must be a power of 2, got {size}"
);
if address_space != RV32_REGISTER_AS {
self.memory_ctx
.update_boundary_merkle_heights(address_space, ptr, size);
}
}
#[inline(always)]
fn should_suspend<F>(exec_state: &mut VmExecState<F, GuestMemory, Self>) -> bool {
if exec_state.ctx.check_and_segment() && exec_state.ctx.suspend_on_segment {
true
} else {
exec_state.ctx.segmentation_ctx.instrets_until_check -= 1;
false
}
}
#[inline(always)]
fn on_terminate<F>(exec_state: &mut VmExecState<F, GuestMemory, Self>) {
exec_state
.ctx
.memory_ctx
.lazy_update_boundary_heights(&mut exec_state.ctx.trace_heights);
exec_state
.ctx
.segmentation_ctx
.create_final_segment(&exec_state.ctx.trace_heights);
}
}
impl<const PAGE_BITS: usize> MeteredExecutionCtxTrait for MeteredCtx<PAGE_BITS> {
#[inline(always)]
fn on_height_change(&mut self, chip_idx: usize, height_delta: u32) {
debug_assert!(
chip_idx < self.trace_heights.len(),
"chip_idx out of bounds"
);
unsafe {
*self.trace_heights.get_unchecked_mut(chip_idx) = self
.trace_heights
.get_unchecked(chip_idx)
.wrapping_add(height_delta);
}
}
}