use std::{array::from_fn, borrow::Borrow, marker::PhantomData};
use openvm_circuit_primitives::{ColumnsAir, StructReflection, StructReflectionHelper};
use openvm_circuit_primitives_derive::AlignedBorrow;
use openvm_cpu_backend::CpuBackend;
use openvm_instructions::{instruction::Instruction, LocalOpcode};
use openvm_stark_backend::{
p3_air::{Air, AirBuilder, BaseAir},
p3_field::PrimeCharacteristicRing,
p3_matrix::{dense::RowMajorMatrix, Matrix},
p3_maybe_rayon::prelude::*,
prover::AirProvingContext,
BaseAirWithPublicValues, PartitionedBaseAir, StarkProtocolConfig, Val,
};
use serde::{Deserialize, Serialize};
use crate::{
arch::RowMajorMatrixArena,
primitives::Chip,
system::memory::{online::TracingMemory, MemoryAuxColsFactory, SharedMemoryHelper},
};
pub trait VmAdapterInterface<T> {
type Reads;
type Writes;
type ProcessedInstruction;
}
pub trait VmAdapterAir<AB: AirBuilder>: BaseAir<AB::F> {
type Interface: VmAdapterInterface<AB::Expr>;
fn eval(
&self,
builder: &mut AB,
local: &[AB::Var],
interface: AdapterAirContext<AB::Expr, Self::Interface>,
);
fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var;
}
pub trait VmCoreAir<AB, I>: BaseAirWithPublicValues<AB::F>
where
AB: AirBuilder,
I: VmAdapterInterface<AB::Expr>,
{
fn eval(
&self,
builder: &mut AB,
local_core: &[AB::Var],
from_pc: AB::Var,
) -> AdapterAirContext<AB::Expr, I>;
fn start_offset(&self) -> usize;
fn start_offset_expr(&self) -> AB::Expr {
AB::Expr::from_usize(self.start_offset())
}
fn expr_to_global_expr(&self, local_expr: impl Into<AB::Expr>) -> AB::Expr {
self.start_offset_expr() + local_expr.into()
}
fn opcode_to_global_expr(&self, local_opcode: impl LocalOpcode) -> AB::Expr {
self.expr_to_global_expr(AB::Expr::from_usize(local_opcode.local_usize()))
}
}
pub struct AdapterAirContext<T, I: VmAdapterInterface<T>> {
pub to_pc: Option<T>,
pub reads: I::Reads,
pub writes: I::Writes,
pub instruction: I::ProcessedInstruction,
}
pub trait TraceFiller<F>: Send + Sync {
fn fill_trace(
&self,
mem_helper: &MemoryAuxColsFactory<F>,
trace: &mut RowMajorMatrix<F>,
rows_used: usize,
) where
F: Send + Sync + Clone,
{
let width = trace.width();
trace.values[..rows_used * width]
.par_chunks_exact_mut(width)
.for_each(|row_slice| {
self.fill_trace_row(mem_helper, row_slice);
});
trace.values[rows_used * width..]
.par_chunks_exact_mut(width)
.for_each(|row_slice| {
self.fill_dummy_trace_row(row_slice);
});
}
fn fill_trace_row(&self, _mem_helper: &MemoryAuxColsFactory<F>, _row_slice: &mut [F]) {
unreachable!("fill_trace_row is not implemented")
}
fn fill_dummy_trace_row(&self, _row_slice: &mut [F]) {
}
fn generate_public_values(&self) -> Vec<F> {
vec![]
}
}
#[derive(derive_new::new)]
pub struct VmChipWrapper<F, FILLER> {
pub inner: FILLER,
pub mem_helper: SharedMemoryHelper<F>,
}
impl<SC, FILLER, RA> Chip<RA, CpuBackend<SC>> for VmChipWrapper<Val<SC>, FILLER>
where
SC: StarkProtocolConfig,
FILLER: TraceFiller<Val<SC>>,
RA: RowMajorMatrixArena<Val<SC>>,
{
fn generate_proving_ctx(&self, arena: RA) -> AirProvingContext<CpuBackend<SC>> {
let rows_used = arena.trace_offset() / arena.width();
let mut trace = arena.into_matrix();
let mem_helper = self.mem_helper.as_borrowed();
self.inner.fill_trace(&mem_helper, &mut trace, rows_used);
AirProvingContext::simple(trace, self.inner.generate_public_values())
}
}
pub trait AdapterTraceExecutor<F>: Clone {
const WIDTH: usize;
type ReadData;
type WriteData;
type RecordMut<'a>
where
Self: 'a;
fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>);
fn read(
&self,
memory: &mut TracingMemory,
instruction: &Instruction<F>,
record: &mut Self::RecordMut<'_>,
) -> Self::ReadData;
fn write(
&self,
memory: &mut TracingMemory,
instruction: &Instruction<F>,
data: Self::WriteData,
record: &mut Self::RecordMut<'_>,
);
}
pub trait AdapterTraceFiller<F>: Send + Sync {
const WIDTH: usize;
fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, adapter_row: &mut [F]);
}
#[derive(Clone, Copy, derive_new::new)]
pub struct VmAirWrapper<A, C> {
pub adapter: A,
pub core: C,
}
impl<F, A, C> BaseAir<F> for VmAirWrapper<A, C>
where
A: BaseAir<F>,
C: BaseAir<F>,
{
fn width(&self) -> usize {
self.adapter.width() + self.core.width()
}
}
impl<F, A, M> BaseAirWithPublicValues<F> for VmAirWrapper<A, M>
where
A: BaseAir<F>,
M: BaseAirWithPublicValues<F>,
{
fn num_public_values(&self) -> usize {
self.core.num_public_values()
}
}
impl<F, A, M> PartitionedBaseAir<F> for VmAirWrapper<A, M>
where
A: BaseAir<F>,
M: BaseAir<F>,
{
}
impl<A, M> ColumnsAir for VmAirWrapper<A, M>
where
A: ColumnsAir,
M: ColumnsAir,
{
fn columns(&self) -> Option<Vec<String>> {
let adapter_cols = self.adapter.columns()?;
let core_cols = self.core.columns()?;
Some(adapter_cols.into_iter().chain(core_cols).collect())
}
}
impl<AB, A, M> Air<AB> for VmAirWrapper<A, M>
where
AB: AirBuilder,
A: VmAdapterAir<AB>,
M: VmCoreAir<AB, A::Interface>,
{
fn eval(&self, builder: &mut AB) {
let main = builder.main();
let local = main.row_slice(0).expect("window should have two elements");
let local: &[AB::Var] = (*local).borrow();
let (local_adapter, local_core) = local.split_at(self.adapter.width());
let ctx = self
.core
.eval(builder, local_core, self.adapter.get_from_pc(local_adapter));
self.adapter.eval(builder, local_adapter, ctx);
}
}
pub struct BasicAdapterInterface<
T,
PI,
const NUM_READS: usize,
const NUM_WRITES: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
>(PhantomData<T>, PhantomData<PI>);
impl<
T,
PI,
const NUM_READS: usize,
const NUM_WRITES: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
> VmAdapterInterface<T>
for BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>
{
type Reads = [[T; READ_SIZE]; NUM_READS];
type Writes = [[T; WRITE_SIZE]; NUM_WRITES];
type ProcessedInstruction = PI;
}
pub struct VecHeapAdapterInterface<
T,
const NUM_READS: usize,
const BLOCKS_PER_READ: usize,
const BLOCKS_PER_WRITE: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
>(PhantomData<T>);
impl<
T,
const NUM_READS: usize,
const BLOCKS_PER_READ: usize,
const BLOCKS_PER_WRITE: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
> VmAdapterInterface<T>
for VecHeapAdapterInterface<
T,
NUM_READS,
BLOCKS_PER_READ,
BLOCKS_PER_WRITE,
READ_SIZE,
WRITE_SIZE,
>
{
type Reads = [[[T; READ_SIZE]; BLOCKS_PER_READ]; NUM_READS];
type Writes = [[T; WRITE_SIZE]; BLOCKS_PER_WRITE];
type ProcessedInstruction = MinimalInstruction<T>;
}
pub struct VecHeapBranchAdapterInterface<
T,
const NUM_READS: usize,
const BLOCKS_PER_READ: usize,
const READ_SIZE: usize,
>(PhantomData<T>);
impl<T, const NUM_READS: usize, const BLOCKS_PER_READ: usize, const READ_SIZE: usize>
VmAdapterInterface<T>
for VecHeapBranchAdapterInterface<T, NUM_READS, BLOCKS_PER_READ, READ_SIZE>
{
type Reads = [[[T; READ_SIZE]; BLOCKS_PER_READ]; NUM_READS];
type Writes = ();
type ProcessedInstruction = ImmInstruction<T>;
}
pub struct FlatInterface<T, PI, const READ_CELLS: usize, const WRITE_CELLS: usize>(
PhantomData<T>,
PhantomData<PI>,
);
impl<T, PI, const READ_CELLS: usize, const WRITE_CELLS: usize> VmAdapterInterface<T>
for FlatInterface<T, PI, READ_CELLS, WRITE_CELLS>
{
type Reads = [T; READ_CELLS];
type Writes = [T; WRITE_CELLS];
type ProcessedInstruction = PI;
}
#[derive(Serialize, Deserialize)]
pub struct DynAdapterInterface<T>(PhantomData<T>);
impl<T> VmAdapterInterface<T> for DynAdapterInterface<T> {
type Reads = DynArray<T>;
type Writes = DynArray<T>;
type ProcessedInstruction = DynArray<T>;
}
#[derive(Clone, Debug, Default)]
pub struct DynArray<T>(pub Vec<T>);
#[repr(C)]
#[derive(AlignedBorrow, StructReflection)]
pub struct MinimalInstruction<T> {
pub is_valid: T,
pub opcode: T,
}
#[repr(C)]
#[derive(AlignedBorrow, StructReflection)]
pub struct ImmInstruction<T> {
pub is_valid: T,
pub opcode: T,
pub immediate: T,
}
#[repr(C)]
#[derive(AlignedBorrow, StructReflection)]
pub struct SignedImmInstruction<T> {
pub is_valid: T,
pub opcode: T,
pub immediate: T,
pub imm_sign: T,
}
mod conversions {
use super::*;
impl<
T,
const NUM_READS: usize,
const BLOCKS_PER_READ: usize,
const BLOCKS_PER_WRITE: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
>
From<
AdapterAirContext<
T,
VecHeapAdapterInterface<
T,
NUM_READS,
BLOCKS_PER_READ,
BLOCKS_PER_WRITE,
READ_SIZE,
WRITE_SIZE,
>,
>,
> for AdapterAirContext<T, DynAdapterInterface<T>>
{
fn from(
ctx: AdapterAirContext<
T,
VecHeapAdapterInterface<
T,
NUM_READS,
BLOCKS_PER_READ,
BLOCKS_PER_WRITE,
READ_SIZE,
WRITE_SIZE,
>,
>,
) -> Self {
AdapterAirContext {
to_pc: ctx.to_pc,
reads: ctx.reads.into(),
writes: ctx.writes.into(),
instruction: ctx.instruction.into(),
}
}
}
impl<
T,
const NUM_READS: usize,
const BLOCKS_PER_READ: usize,
const BLOCKS_PER_WRITE: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
> From<AdapterAirContext<T, DynAdapterInterface<T>>>
for AdapterAirContext<
T,
VecHeapAdapterInterface<
T,
NUM_READS,
BLOCKS_PER_READ,
BLOCKS_PER_WRITE,
READ_SIZE,
WRITE_SIZE,
>,
>
{
fn from(ctx: AdapterAirContext<T, DynAdapterInterface<T>>) -> Self {
AdapterAirContext {
to_pc: ctx.to_pc,
reads: ctx.reads.into(),
writes: ctx.writes.into(),
instruction: ctx.instruction.into(),
}
}
}
impl<
T,
PI: Into<MinimalInstruction<T>>,
const BASIC_NUM_READS: usize,
const BASIC_NUM_WRITES: usize,
const NUM_READS: usize,
const BLOCKS_PER_READ: usize,
const BLOCKS_PER_WRITE: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
>
From<
AdapterAirContext<
T,
BasicAdapterInterface<
T,
PI,
BASIC_NUM_READS,
BASIC_NUM_WRITES,
READ_SIZE,
WRITE_SIZE,
>,
>,
>
for AdapterAirContext<
T,
VecHeapAdapterInterface<
T,
NUM_READS,
BLOCKS_PER_READ,
BLOCKS_PER_WRITE,
READ_SIZE,
WRITE_SIZE,
>,
>
{
fn from(
ctx: AdapterAirContext<
T,
BasicAdapterInterface<
T,
PI,
BASIC_NUM_READS,
BASIC_NUM_WRITES,
READ_SIZE,
WRITE_SIZE,
>,
>,
) -> Self {
assert_eq!(BASIC_NUM_READS, NUM_READS * BLOCKS_PER_READ);
let mut reads_it = ctx.reads.into_iter();
let reads = from_fn(|_| from_fn(|_| reads_it.next().unwrap()));
assert_eq!(BASIC_NUM_WRITES, BLOCKS_PER_WRITE);
let mut writes_it = ctx.writes.into_iter();
let writes = from_fn(|_| writes_it.next().unwrap());
AdapterAirContext {
to_pc: ctx.to_pc,
reads,
writes,
instruction: ctx.instruction.into(),
}
}
}
impl<
T,
PI,
const NUM_READS: usize,
const NUM_WRITES: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
const READ_CELLS: usize,
const WRITE_CELLS: usize,
>
From<
AdapterAirContext<
T,
BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>,
>,
> for AdapterAirContext<T, FlatInterface<T, PI, READ_CELLS, WRITE_CELLS>>
{
fn from(
ctx: AdapterAirContext<
T,
BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>,
>,
) -> AdapterAirContext<T, FlatInterface<T, PI, READ_CELLS, WRITE_CELLS>> {
assert_eq!(READ_CELLS, NUM_READS * READ_SIZE);
assert_eq!(WRITE_CELLS, NUM_WRITES * WRITE_SIZE);
let mut reads_it = ctx.reads.into_iter().flatten();
let reads = from_fn(|_| reads_it.next().unwrap());
let mut writes_it = ctx.writes.into_iter().flatten();
let writes = from_fn(|_| writes_it.next().unwrap());
AdapterAirContext {
to_pc: ctx.to_pc,
reads,
writes,
instruction: ctx.instruction,
}
}
}
impl<
T,
PI,
const NUM_READS: usize,
const NUM_WRITES: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
const READ_CELLS: usize,
const WRITE_CELLS: usize,
> From<AdapterAirContext<T, FlatInterface<T, PI, READ_CELLS, WRITE_CELLS>>>
for AdapterAirContext<
T,
BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>,
>
{
fn from(
AdapterAirContext {
to_pc,
reads,
writes,
instruction,
}: AdapterAirContext<T, FlatInterface<T, PI, READ_CELLS, WRITE_CELLS>>,
) -> AdapterAirContext<
T,
BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>,
> {
assert_eq!(READ_CELLS, NUM_READS * READ_SIZE);
assert_eq!(WRITE_CELLS, NUM_WRITES * WRITE_SIZE);
let mut reads_it = reads.into_iter();
let reads: [[T; READ_SIZE]; NUM_READS] =
from_fn(|_| from_fn(|_| reads_it.next().unwrap()));
let mut writes_it = writes.into_iter();
let writes: [[T; WRITE_SIZE]; NUM_WRITES] =
from_fn(|_| from_fn(|_| writes_it.next().unwrap()));
AdapterAirContext {
to_pc,
reads,
writes,
instruction,
}
}
}
impl<T> From<Vec<T>> for DynArray<T> {
fn from(v: Vec<T>) -> Self {
Self(v)
}
}
impl<T> From<DynArray<T>> for Vec<T> {
fn from(v: DynArray<T>) -> Vec<T> {
v.0
}
}
impl<T, const N: usize, const M: usize> From<[[T; N]; M]> for DynArray<T> {
fn from(v: [[T; N]; M]) -> Self {
Self(v.into_iter().flatten().collect())
}
}
impl<T, const N: usize, const M: usize> From<DynArray<T>> for [[T; N]; M] {
fn from(v: DynArray<T>) -> Self {
assert_eq!(v.0.len(), N * M, "Incorrect vector length {}", v.0.len());
let mut it = v.0.into_iter();
from_fn(|_| from_fn(|_| it.next().unwrap()))
}
}
impl<T, const N: usize, const M: usize, const R: usize> From<[[[T; N]; M]; R]> for DynArray<T> {
fn from(v: [[[T; N]; M]; R]) -> Self {
Self(
v.into_iter()
.flat_map(|x| x.into_iter().flatten())
.collect(),
)
}
}
impl<T, const N: usize, const M: usize, const R: usize> From<DynArray<T>> for [[[T; N]; M]; R] {
fn from(v: DynArray<T>) -> Self {
assert_eq!(
v.0.len(),
N * M * R,
"Incorrect vector length {}",
v.0.len()
);
let mut it = v.0.into_iter();
from_fn(|_| from_fn(|_| from_fn(|_| it.next().unwrap())))
}
}
impl<T, const N: usize, const M1: usize, const M2: usize> From<([[T; N]; M1], [[T; N]; M2])>
for DynArray<T>
{
fn from(v: ([[T; N]; M1], [[T; N]; M2])) -> Self {
let vec =
v.0.into_iter()
.flatten()
.chain(v.1.into_iter().flatten())
.collect();
Self(vec)
}
}
impl<T, const N: usize, const M1: usize, const M2: usize> From<DynArray<T>>
for ([[T; N]; M1], [[T; N]; M2])
{
fn from(v: DynArray<T>) -> Self {
assert_eq!(
v.0.len(),
N * (M1 + M2),
"Incorrect vector length {}",
v.0.len()
);
let mut it = v.0.into_iter();
(
from_fn(|_| from_fn(|_| it.next().unwrap())),
from_fn(|_| from_fn(|_| it.next().unwrap())),
)
}
}
impl<
T,
PI: Into<DynArray<T>>,
const NUM_READS: usize,
const NUM_WRITES: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
>
From<
AdapterAirContext<
T,
BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>,
>,
> for AdapterAirContext<T, DynAdapterInterface<T>>
{
fn from(
ctx: AdapterAirContext<
T,
BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>,
>,
) -> Self {
AdapterAirContext {
to_pc: ctx.to_pc,
reads: ctx.reads.into(),
writes: ctx.writes.into(),
instruction: ctx.instruction.into(),
}
}
}
impl<
T,
PI,
const NUM_READS: usize,
const NUM_WRITES: usize,
const READ_SIZE: usize,
const WRITE_SIZE: usize,
> From<AdapterAirContext<T, DynAdapterInterface<T>>>
for AdapterAirContext<
T,
BasicAdapterInterface<T, PI, NUM_READS, NUM_WRITES, READ_SIZE, WRITE_SIZE>,
>
where
PI: From<DynArray<T>>,
{
fn from(ctx: AdapterAirContext<T, DynAdapterInterface<T>>) -> Self {
AdapterAirContext {
to_pc: ctx.to_pc,
reads: ctx.reads.into(),
writes: ctx.writes.into(),
instruction: ctx.instruction.into(),
}
}
}
impl<T: Clone, PI: Into<DynArray<T>>, const READ_CELLS: usize, const WRITE_CELLS: usize>
From<AdapterAirContext<T, FlatInterface<T, PI, READ_CELLS, WRITE_CELLS>>>
for AdapterAirContext<T, DynAdapterInterface<T>>
{
fn from(ctx: AdapterAirContext<T, FlatInterface<T, PI, READ_CELLS, WRITE_CELLS>>) -> Self {
AdapterAirContext {
to_pc: ctx.to_pc,
reads: ctx.reads.to_vec().into(),
writes: ctx.writes.to_vec().into(),
instruction: ctx.instruction.into(),
}
}
}
impl<T> From<MinimalInstruction<T>> for DynArray<T> {
fn from(m: MinimalInstruction<T>) -> Self {
Self(vec![m.is_valid, m.opcode])
}
}
impl<T> From<DynArray<T>> for MinimalInstruction<T> {
fn from(m: DynArray<T>) -> Self {
let mut m = m.0.into_iter();
MinimalInstruction {
is_valid: m.next().unwrap(),
opcode: m.next().unwrap(),
}
}
}
impl<T> From<DynArray<T>> for ImmInstruction<T> {
fn from(m: DynArray<T>) -> Self {
let mut m = m.0.into_iter();
ImmInstruction {
is_valid: m.next().unwrap(),
opcode: m.next().unwrap(),
immediate: m.next().unwrap(),
}
}
}
impl<T> From<ImmInstruction<T>> for DynArray<T> {
fn from(instruction: ImmInstruction<T>) -> Self {
DynArray::from(vec![
instruction.is_valid,
instruction.opcode,
instruction.immediate,
])
}
}
impl<
T,
const BASIC_NUM_READS: usize,
const NUM_READS: usize,
const BLOCKS_PER_READ: usize,
const READ_SIZE: usize,
>
From<
AdapterAirContext<
T,
BasicAdapterInterface<T, ImmInstruction<T>, BASIC_NUM_READS, 0, READ_SIZE, 0>,
>,
>
for AdapterAirContext<
T,
VecHeapBranchAdapterInterface<T, NUM_READS, BLOCKS_PER_READ, READ_SIZE>,
>
{
fn from(
ctx: AdapterAirContext<
T,
BasicAdapterInterface<T, ImmInstruction<T>, BASIC_NUM_READS, 0, READ_SIZE, 0>,
>,
) -> Self {
assert_eq!(BASIC_NUM_READS, NUM_READS * BLOCKS_PER_READ);
let mut reads_it = ctx.reads.into_iter();
let reads = from_fn(|_| from_fn(|_| reads_it.next().unwrap()));
AdapterAirContext {
to_pc: ctx.to_pc,
reads,
writes: (),
instruction: ctx.instruction,
}
}
}
}