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, PrimeCharacteristicRing, 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).expect("window should have two elements");
76        let &PhantomCols {
77            pc,
78            operands,
79            timestamp,
80            is_valid,
81        } = (*local).borrow();
82
83        builder.assert_bool(is_valid);
84        self.execution_bridge
85            .execute_and_increment_or_set_pc(
86                self.phantom_opcode.to_field::<AB::F>(),
87                operands,
88                ExecutionState::<AB::Expr>::new(pc, timestamp),
89                AB::Expr::ONE,
90                PcIncOrSet::Inc(AB::Expr::from_u32(DEFAULT_PC_STEP)),
91            )
92            .eval(builder, is_valid);
93    }
94}
95
96#[repr(C)]
97#[derive(AlignedBytesBorrow, Debug, Clone)]
98pub struct PhantomRecord {
99    pub pc: u32,
100    pub operands: [u32; NUM_PHANTOM_OPERANDS],
101    pub timestamp: u32,
102}
103
104/// `PhantomChip` is a special executor because it is stateful and stores all the phantom
105/// sub-executors.
106#[derive(Clone, derive_new::new)]
107pub struct PhantomExecutor<F> {
108    pub(crate) phantom_executors: FxHashMap<PhantomDiscriminant, Arc<dyn PhantomSubExecutor<F>>>,
109    phantom_opcode: VmOpcode,
110}
111
112pub struct PhantomFiller;
113pub type PhantomChip<F> = VmChipWrapper<F, PhantomFiller>;
114
115impl<F, RA> PreflightExecutor<F, RA> for PhantomExecutor<F>
116where
117    F: PrimeField32,
118    for<'buf> RA: RecordArena<'buf, EmptyMultiRowLayout, &'buf mut PhantomRecord>,
119{
120    fn execute(
121        &self,
122        state: VmStateMut<F, TracingMemory, RA>,
123        instruction: &Instruction<F>,
124    ) -> Result<(), ExecutionError> {
125        let record: &mut PhantomRecord = state.ctx.alloc(EmptyMultiRowLayout::default());
126        let pc = *state.pc;
127        record.pc = pc;
128        record.timestamp = state.memory.timestamp;
129        let [a, b, c] = [instruction.a, instruction.b, instruction.c].map(|x| x.as_canonical_u32());
130        record.operands = [a, b, c];
131
132        debug_assert_eq!(instruction.opcode, self.phantom_opcode);
133        let discriminant = PhantomDiscriminant(c as u16);
134        if let Some(sys) = SysPhantom::from_repr(discriminant.0) {
135            tracing::trace!("pc: {pc:#x} | system phantom: {sys:?}");
136            match sys {
137                SysPhantom::DebugPanic => {
138                    #[cfg(all(
139                        feature = "metrics",
140                        any(debug_assertions, feature = "perf-metrics")
141                    ))]
142                    {
143                        let metrics = state.metrics;
144                        metrics.update_backtrace(pc);
145                        if let Some(mut backtrace) = metrics.prev_backtrace.take() {
146                            backtrace.resolve();
147                            eprintln!("openvm program failure; backtrace:\n{backtrace:?}");
148                        } else {
149                            eprintln!("openvm program failure; no backtrace");
150                        }
151                    }
152                    return Err(ExecutionError::Fail {
153                        pc,
154                        msg: "DebugPanic",
155                    });
156                }
157                #[cfg(feature = "perf-metrics")]
158                SysPhantom::CtStart => {
159                    let metrics = state.metrics;
160                    if let Some(info) = metrics.debug_infos.get(pc) {
161                        metrics.cycle_tracker.start(info.dsl_instruction.clone());
162                    }
163                }
164                #[cfg(feature = "perf-metrics")]
165                SysPhantom::CtEnd => {
166                    let metrics = state.metrics;
167                    if let Some(info) = metrics.debug_infos.get(pc) {
168                        metrics.cycle_tracker.end(info.dsl_instruction.clone());
169                    }
170                }
171                _ => {}
172            }
173        } else {
174            let sub_executor = self.phantom_executors.get(&discriminant).unwrap();
175            sub_executor
176                .phantom_execute(
177                    &state.memory.data,
178                    state.streams,
179                    state.rng,
180                    discriminant,
181                    a,
182                    b,
183                    (c >> 16) as u16,
184                )
185                .map_err(|err| ExecutionError::Phantom {
186                    pc,
187                    discriminant,
188                    inner: err,
189                })?;
190        }
191        *state.pc += DEFAULT_PC_STEP;
192        state.memory.increment_timestamp();
193
194        Ok(())
195    }
196
197    fn get_opcode_name(&self, _: usize) -> String {
198        format!("{:?}", SystemOpcode::PHANTOM)
199    }
200}
201
202impl<F: Field> TraceFiller<F> for PhantomFiller {
203    fn fill_trace_row(&self, _mem_helper: &MemoryAuxColsFactory<F>, mut row_slice: &mut [F]) {
204        // SAFETY: assume that row has size PhantomCols::<F>::width()
205        let record: &PhantomRecord = unsafe { get_record_from_slice(&mut row_slice, ()) };
206        let row: &mut PhantomCols<F> = row_slice.borrow_mut();
207        // SAFETY: must assign in reverse order of column struct to prevent overwriting
208        // borrowed data
209        row.is_valid = F::ONE;
210        row.timestamp = F::from_u32(record.timestamp);
211        row.operands[2] = F::from_u32(record.operands[2]);
212        row.operands[1] = F::from_u32(record.operands[1]);
213        row.operands[0] = F::from_u32(record.operands[0]);
214        row.pc = F::from_u32(record.pc)
215    }
216}
217
218pub struct NopPhantomExecutor;
219pub struct CycleStartPhantomExecutor;
220pub struct CycleEndPhantomExecutor;
221
222impl<F> PhantomSubExecutor<F> for NopPhantomExecutor {
223    #[inline(always)]
224    fn phantom_execute(
225        &self,
226        _memory: &GuestMemory,
227        _streams: &mut Streams<F>,
228        _rng: &mut StdRng,
229        _discriminant: PhantomDiscriminant,
230        _a: u32,
231        _b: u32,
232        _c_upper: u16,
233    ) -> eyre::Result<()> {
234        Ok(())
235    }
236}
237
238impl<F> PhantomSubExecutor<F> for CycleStartPhantomExecutor {
239    #[inline(always)]
240    fn phantom_execute(
241        &self,
242        _memory: &GuestMemory,
243        _streams: &mut Streams<F>,
244        _rng: &mut StdRng,
245        _discriminant: PhantomDiscriminant,
246        _a: u32,
247        _b: u32,
248        _c_upper: u16,
249    ) -> eyre::Result<()> {
250        // Cycle tracking is implemented separately only in Preflight Execution
251        Ok(())
252    }
253}
254
255impl<F> PhantomSubExecutor<F> for CycleEndPhantomExecutor {
256    #[inline(always)]
257    fn phantom_execute(
258        &self,
259        _memory: &GuestMemory,
260        _streams: &mut Streams<F>,
261        _rng: &mut StdRng,
262        _discriminant: PhantomDiscriminant,
263        _a: u32,
264        _b: u32,
265        _c_upper: u16,
266    ) -> eyre::Result<()> {
267        // Cycle tracking is implemented separately only in Preflight Execution
268        Ok(())
269    }
270}