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, 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
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);
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#[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 let record: &PhantomRecord = unsafe { get_record_from_slice(&mut row_slice, ()) };
205 let row: &mut PhantomCols<F> = row_slice.borrow_mut();
206 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 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 Ok(())
268 }
269}