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