openvm_circuit/system/phantom/
mod.rs1use 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
42const NUM_PHANTOM_OPERANDS: usize = 3;
46
47#[derive(Clone, Debug)]
48pub struct PhantomAir {
49 pub execution_bridge: ExecutionBridge,
50 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#[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 let record: &PhantomRecord = unsafe { get_record_from_slice(&mut row_slice, ()) };
206 let row: &mut PhantomCols<F> = row_slice.borrow_mut();
207 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 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 Ok(())
269 }
270}