openvm_circuit/system/phantom/
mod.rs

1//! Chip to handle phantom instructions.
2//! The Air will always constrain a NOP which advances pc by DEFAULT_PC_STEP.
3//! The runtime executor will execute different phantom instructions that may
4//! affect trace generation based on the operand.
5use std::{
6    borrow::{Borrow, BorrowMut},
7    sync::Arc,
8};
9
10use openvm_circuit_primitives::{
11    AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
12};
13use openvm_circuit_primitives_derive::AlignedBorrow;
14use openvm_instructions::{
15    instruction::Instruction, program::DEFAULT_PC_STEP, PhantomDiscriminant, SysPhantom,
16    SystemOpcode, VmOpcode,
17};
18use openvm_stark_backend::{
19    interaction::InteractionBuilder,
20    p3_air::{Air, AirBuilder, BaseAir},
21    p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
22    p3_matrix::Matrix,
23    BaseAirWithPublicValues, PartitionedBaseAir,
24};
25use rand::rngs::StdRng;
26use rustc_hash::FxHashMap;
27use serde::{Deserialize, Serialize};
28use serde_big_array::BigArray;
29
30use super::memory::online::{GuestMemory, TracingMemory};
31use crate::{
32    arch::{
33        get_record_from_slice, EmptyMultiRowLayout, ExecutionBridge, ExecutionError,
34        ExecutionState, PcIncOrSet, PhantomSubExecutor, PreflightExecutor, RecordArena, Streams,
35        TraceFiller, VmChipWrapper, VmStateMut,
36    },
37    system::memory::MemoryAuxColsFactory,
38};
39
40mod execution;
41#[cfg(test)]
42mod tests;
43
44/// PhantomAir still needs columns for each nonzero operand in a phantom instruction.
45/// We currently allow `a,b,c` where the lower 16 bits of `c` are used as the [PhantomInstruction]
46/// discriminant.
47const NUM_PHANTOM_OPERANDS: usize = 3;
48
49#[derive(Clone, Debug, ColumnsAir)]
50#[columns_via(PhantomCols<u8>)]
51pub struct PhantomAir {
52    pub execution_bridge: ExecutionBridge,
53    /// Global opcode for PhantomOpcode
54    pub phantom_opcode: VmOpcode,
55}
56
57#[repr(C)]
58#[derive(AlignedBorrow, StructReflection, Copy, Clone, Serialize, Deserialize)]
59pub struct PhantomCols<T> {
60    pub pc: T,
61    #[serde(with = "BigArray")]
62    pub operands: [T; NUM_PHANTOM_OPERANDS],
63    pub timestamp: T,
64    pub is_valid: T,
65}
66
67impl<F: Field> BaseAir<F> for PhantomAir {
68    fn width(&self) -> usize {
69        PhantomCols::<F>::width()
70    }
71}
72impl<F: Field> PartitionedBaseAir<F> for PhantomAir {}
73impl<F: Field> BaseAirWithPublicValues<F> for PhantomAir {}
74
75impl<AB: AirBuilder + InteractionBuilder> Air<AB> for PhantomAir {
76    fn eval(&self, builder: &mut AB) {
77        let main = builder.main();
78        let local = main.row_slice(0).expect("window should have two elements");
79        let &PhantomCols {
80            pc,
81            operands,
82            timestamp,
83            is_valid,
84        } = (*local).borrow();
85
86        builder.assert_bool(is_valid);
87        self.execution_bridge
88            .execute_and_increment_or_set_pc(
89                self.phantom_opcode.to_field::<AB::F>(),
90                operands,
91                ExecutionState::<AB::Expr>::new(pc, timestamp),
92                AB::Expr::ONE,
93                PcIncOrSet::Inc(AB::Expr::from_u32(DEFAULT_PC_STEP)),
94            )
95            .eval(builder, is_valid);
96    }
97}
98
99#[repr(C)]
100#[derive(AlignedBytesBorrow, Debug, Clone)]
101pub struct PhantomRecord {
102    pub pc: u32,
103    pub operands: [u32; NUM_PHANTOM_OPERANDS],
104    pub timestamp: u32,
105}
106
107/// `PhantomChip` is a special executor because it is stateful and stores all the phantom
108/// sub-executors.
109#[derive(Clone, derive_new::new)]
110pub struct PhantomExecutor<F> {
111    pub(crate) phantom_executors: FxHashMap<PhantomDiscriminant, Arc<dyn PhantomSubExecutor<F>>>,
112    phantom_opcode: VmOpcode,
113}
114
115pub struct PhantomFiller;
116pub type PhantomChip<F> = VmChipWrapper<F, PhantomFiller>;
117
118impl<F, RA> PreflightExecutor<F, RA> for PhantomExecutor<F>
119where
120    F: PrimeField32,
121    for<'buf> RA: RecordArena<'buf, EmptyMultiRowLayout, &'buf mut PhantomRecord>,
122{
123    fn execute(
124        &self,
125        state: VmStateMut<F, TracingMemory, RA>,
126        instruction: &Instruction<F>,
127    ) -> Result<(), ExecutionError> {
128        let record: &mut PhantomRecord = state.ctx.alloc(EmptyMultiRowLayout::default());
129        let pc = *state.pc;
130        record.pc = pc;
131        record.timestamp = state.memory.timestamp;
132        let [a, b, c] = [instruction.a, instruction.b, instruction.c].map(|x| x.as_canonical_u32());
133        record.operands = [a, b, c];
134
135        debug_assert_eq!(instruction.opcode, self.phantom_opcode);
136        let discriminant = PhantomDiscriminant(c as u16);
137        if let Some(sys) = SysPhantom::from_repr(discriminant.0) {
138            tracing::trace!("pc: {pc:#x} | system phantom: {sys:?}");
139            match sys {
140                SysPhantom::DebugPanic => {
141                    #[cfg(all(
142                        feature = "metrics",
143                        any(debug_assertions, feature = "perf-metrics")
144                    ))]
145                    {
146                        let metrics = state.metrics;
147                        metrics.update_backtrace(pc);
148                        if let Some(mut backtrace) = metrics.prev_backtrace.take() {
149                            backtrace.resolve();
150                            eprintln!("openvm program failure; backtrace:\n{backtrace:?}");
151                        } else {
152                            eprintln!("openvm program failure; no backtrace");
153                        }
154                    }
155                    return Err(ExecutionError::Fail {
156                        pc,
157                        msg: "DebugPanic",
158                    });
159                }
160                #[cfg(feature = "perf-metrics")]
161                SysPhantom::CtStart => {
162                    let metrics = state.metrics;
163                    if let Some(info) = metrics.debug_infos.get(pc) {
164                        metrics.cycle_tracker.start(info.dsl_instruction.clone());
165                    }
166                }
167                #[cfg(feature = "perf-metrics")]
168                SysPhantom::CtEnd => {
169                    let metrics = state.metrics;
170                    if let Some(info) = metrics.debug_infos.get(pc) {
171                        metrics.cycle_tracker.end(info.dsl_instruction.clone());
172                    }
173                }
174                _ => {}
175            }
176        } else {
177            let sub_executor = self.phantom_executors.get(&discriminant).unwrap();
178            sub_executor
179                .phantom_execute(
180                    &state.memory.data,
181                    state.streams,
182                    state.rng,
183                    discriminant,
184                    a,
185                    b,
186                    (c >> 16) as u16,
187                )
188                .map_err(|err| ExecutionError::Phantom {
189                    pc,
190                    discriminant,
191                    inner: err,
192                })?;
193        }
194        *state.pc += DEFAULT_PC_STEP;
195        state.memory.increment_timestamp();
196
197        Ok(())
198    }
199
200    fn get_opcode_name(&self, _: usize) -> String {
201        format!("{:?}", SystemOpcode::PHANTOM)
202    }
203}
204
205impl<F: Field> TraceFiller<F> for PhantomFiller {
206    fn fill_trace_row(&self, _mem_helper: &MemoryAuxColsFactory<F>, mut row_slice: &mut [F]) {
207        // SAFETY: assume that row has size PhantomCols::<F>::width()
208        let record: &PhantomRecord = unsafe { get_record_from_slice(&mut row_slice, ()) };
209        let row: &mut PhantomCols<F> = row_slice.borrow_mut();
210        // SAFETY: must assign in reverse order of column struct to prevent overwriting
211        // borrowed data
212        row.is_valid = F::ONE;
213        row.timestamp = F::from_u32(record.timestamp);
214        row.operands[2] = F::from_u32(record.operands[2]);
215        row.operands[1] = F::from_u32(record.operands[1]);
216        row.operands[0] = F::from_u32(record.operands[0]);
217        row.pc = F::from_u32(record.pc)
218    }
219}
220
221pub struct NopPhantomExecutor;
222pub struct CycleStartPhantomExecutor;
223pub struct CycleEndPhantomExecutor;
224
225impl<F> PhantomSubExecutor<F> for NopPhantomExecutor {
226    #[inline(always)]
227    fn phantom_execute(
228        &self,
229        _memory: &GuestMemory,
230        _streams: &mut Streams<F>,
231        _rng: &mut StdRng,
232        _discriminant: PhantomDiscriminant,
233        _a: u32,
234        _b: u32,
235        _c_upper: u16,
236    ) -> eyre::Result<()> {
237        Ok(())
238    }
239}
240
241impl<F> PhantomSubExecutor<F> for CycleStartPhantomExecutor {
242    #[inline(always)]
243    fn phantom_execute(
244        &self,
245        _memory: &GuestMemory,
246        _streams: &mut Streams<F>,
247        _rng: &mut StdRng,
248        _discriminant: PhantomDiscriminant,
249        _a: u32,
250        _b: u32,
251        _c_upper: u16,
252    ) -> eyre::Result<()> {
253        // Cycle tracking is implemented separately only in Preflight Execution
254        Ok(())
255    }
256}
257
258impl<F> PhantomSubExecutor<F> for CycleEndPhantomExecutor {
259    #[inline(always)]
260    fn phantom_execute(
261        &self,
262        _memory: &GuestMemory,
263        _streams: &mut Streams<F>,
264        _rng: &mut StdRng,
265        _discriminant: PhantomDiscriminant,
266        _a: u32,
267        _b: u32,
268        _c_upper: u16,
269    ) -> eyre::Result<()> {
270        // Cycle tracking is implemented separately only in Preflight Execution
271        Ok(())
272    }
273}