openvm_circuit/system/phantom/
mod.rs1use 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
44const 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 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#[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 let record: &PhantomRecord = unsafe { get_record_from_slice(&mut row_slice, ()) };
209 let row: &mut PhantomCols<F> = row_slice.borrow_mut();
210 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 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 Ok(())
272 }
273}