@@ -5,11 +5,11 @@ use crate::{
55 bytecode:: {
66 instruction:: { Instruction , jump:: JumpIfNotZeroInstruction } ,
77 operand:: MemOrFp ,
8- program:: Program ,
8+ program:: { ExecutionPassResult , ExecutionResult , Program } ,
99 } ,
1010 constant:: {
11- CODE_SEGMENT , DIMENSION , F , POSEIDON_16_NULL_HASH_PTR , POSEIDON_24_NULL_HASH_PTR ,
12- PUBLIC_INPUT_START , ZERO_VEC_PTR ,
11+ CODE_SEGMENT , DIMENSION , F , MAX_MEMORY_SIZE , POSEIDON_16_NULL_HASH_PTR ,
12+ POSEIDON_24_NULL_HASH_PTR , PUBLIC_INPUT_START , ZERO_VEC_PTR ,
1313 } ,
1414 context:: run_context:: RunContext ,
1515 errors:: { memory:: MemoryError , vm:: VirtualMachineError } ,
@@ -23,6 +23,15 @@ pub struct VirtualMachine<Perm16, Perm24> {
2323 pub poseidon2_16 : Perm16 ,
2424 pub poseidon2_24 : Perm24 ,
2525 pub ( crate ) program : Program ,
26+ // Fields for tracing execution and gathering statistics.
27+ pub ( crate ) pcs : Vec < MemoryAddress > ,
28+ pub ( crate ) fps : Vec < MemoryAddress > ,
29+ pub ( crate ) cpu_cycles : u64 ,
30+ pub ( crate ) poseidon16_calls : u64 ,
31+ pub ( crate ) poseidon24_calls : u64 ,
32+ pub ( crate ) ext_mul_calls : u64 ,
33+ // A string buffer to hold output from the Print hint.
34+ pub ( crate ) std_out : String ,
2635}
2736
2837impl < Perm16 , Perm24 > VirtualMachine < Perm16 , Perm24 >
3746 program : Program :: default ( ) ,
3847 poseidon2_16,
3948 poseidon2_24,
49+ // Initialize new fields for tracing and stats.
50+ pcs : Vec :: new ( ) ,
51+ fps : Vec :: new ( ) ,
52+ cpu_cycles : 0 ,
53+ poseidon16_calls : 0 ,
54+ poseidon24_calls : 0 ,
55+ ext_mul_calls : 0 ,
56+ std_out : String :: new ( ) ,
4057 }
4158 }
4259
@@ -261,6 +278,145 @@ where
261278 // update the pc and fp registers to prepare for the next cycle.
262279 self . update_registers ( instruction)
263280 }
281+
282+ /// Resets the machine's state to prepare for a new execution run.
283+ fn reset_state ( & mut self ) {
284+ self . run_context = RunContext :: default ( ) ;
285+ self . memory_manager = MemoryManager :: default ( ) ;
286+ self . program = Program :: default ( ) ;
287+ self . pcs . clear ( ) ;
288+ self . fps . clear ( ) ;
289+ self . cpu_cycles = 0 ;
290+ self . poseidon16_calls = 0 ;
291+ self . poseidon24_calls = 0 ;
292+ self . ext_mul_calls = 0 ;
293+ self . std_out . clear ( ) ;
294+ }
295+
296+ /// Updates execution statistics based on the instruction that was just executed.
297+ const fn update_statistics ( & mut self , instruction : & Instruction ) {
298+ match instruction {
299+ Instruction :: Poseidon2_16 ( _) => self . poseidon16_calls += 1 ,
300+ Instruction :: Poseidon2_24 ( _) => self . poseidon24_calls += 1 ,
301+ Instruction :: ExtensionMul ( _) => self . ext_mul_calls += 1 ,
302+ _ => ( ) , // Other instructions do not have special counters.
303+ }
304+ }
305+
306+ /// Executes a program, returning the final machine state and execution trace.
307+ ///
308+ /// This function implements the two-pass execution strategy:
309+ /// 1. A first pass ("dry run") is executed to determine the exact amount of non-vectorized
310+ /// runtime memory (`no_vec_runtime_memory`) required.
311+ /// 2. A second, final pass is executed with the correctly sized memory allocations.
312+ pub fn run ( & mut self , program : & Program ) -> Result < ExecutionResult , VirtualMachineError > {
313+ // First pass: execute with an initial guess for non-vector memory to determine the actual required size.
314+ let first_pass_result =
315+ self . execute_program ( program. clone ( ) , MAX_MEMORY_SIZE / 2 , false ) ?;
316+ let no_vec_runtime_memory =
317+ self . run_context . ap . offset - first_pass_result. initial_ap . offset ;
318+
319+ // Second pass: execute with the exact memory size determined from the first run.
320+ let final_result = self . execute_program ( program. clone ( ) , no_vec_runtime_memory, true ) ?;
321+
322+ Ok ( final_result. result )
323+ }
324+
325+ /// The core execution loop of the virtual machine.
326+ fn execute_program (
327+ & mut self ,
328+ program : Program ,
329+ no_vec_runtime_memory : usize ,
330+ final_execution : bool ,
331+ ) -> Result < ExecutionPassResult , VirtualMachineError > {
332+ // Reset state for the current run.
333+ self . reset_state ( ) ;
334+
335+ // Setup the VM state: load program, inputs, and initialize memory/registers.
336+ self . setup ( program, no_vec_runtime_memory) ?;
337+
338+ // Store initial allocation pointers to calculate memory usage later.
339+ let initial_ap = self . run_context . ap ;
340+ let initial_ap_vec = self . run_context . ap_vectorized ;
341+
342+ // Main execution loop: continues until the program counter (pc) reaches the designated end address.
343+ while self . run_context . pc . offset != self . program . bytecode . ending_pc {
344+ // Ensure the program counter is within the bounds of the loaded bytecode instructions.
345+ if self . run_context . pc . offset >= self . program . bytecode . instructions . len ( ) {
346+ return Err ( VirtualMachineError :: PCOutOfBounds ) ;
347+ }
348+
349+ // Record current pc and fp for the execution trace.
350+ self . pcs . push ( self . run_context . pc ) ;
351+ self . fps . push ( self . run_context . fp ) ;
352+
353+ self . cpu_cycles += 1 ;
354+
355+ // Execute Hints: process non-deterministic hints associated with the current pc.
356+ if let Some ( hints) = self . program . bytecode . hints . get ( & self . run_context . pc . offset ) {
357+ for hint in hints {
358+ hint. execute ( & mut self . memory_manager , & mut self . run_context ) ?;
359+ }
360+ }
361+
362+ // Fetch and Execute Instruction.
363+ let instruction =
364+ self . program . bytecode . instructions [ self . run_context . pc . offset ] . clone ( ) ;
365+ self . run_instruction ( & instruction) ?;
366+
367+ // Update statistics based on instruction type.
368+ self . update_statistics ( & instruction) ;
369+ }
370+
371+ // Record the final state of pc and fp.
372+ self . pcs . push ( self . run_context . pc ) ;
373+ self . fps . push ( self . run_context . fp ) ;
374+
375+ if final_execution {
376+ self . log_summary ( ) ;
377+ }
378+
379+ Ok ( ExecutionPassResult {
380+ result : ExecutionResult {
381+ memory_manager : self . memory_manager . clone ( ) ,
382+ pcs : self . pcs . clone ( ) ,
383+ fps : self . fps . clone ( ) ,
384+ } ,
385+ initial_ap,
386+ initial_ap_vec,
387+ } )
388+ }
389+
390+ /// Logs a summary of the execution results to standard output.
391+ ///
392+ /// TODO: remove this in the future (helper for debugging).
393+ fn log_summary ( & self ) {
394+ if !self . std_out . is_empty ( ) {
395+ print ! ( "{}" , self . std_out) ;
396+ }
397+
398+ println ! (
399+ "\n Bytecode size: {}" ,
400+ self . program. bytecode. instructions. len( )
401+ ) ;
402+ println ! ( "Public input size: {}" , self . program. public_input. len( ) ) ;
403+ println ! ( "Private input size: {}" , self . program. private_input. len( ) ) ;
404+ println ! ( "Executed {} instructions" , self . cpu_cycles) ;
405+
406+ let total_poseidon_calls = self . poseidon16_calls + self . poseidon24_calls ;
407+ if total_poseidon_calls > 0 {
408+ println ! (
409+ "Poseidon2_16 calls: {}, Poseidon2_24 calls: {} (1 poseidon per {} instructions)" ,
410+ self . poseidon16_calls,
411+ self . poseidon24_calls,
412+ self . cpu_cycles / total_poseidon_calls
413+ ) ;
414+ }
415+
416+ if self . ext_mul_calls > 0 {
417+ println ! ( "ExtensionMul calls: {}" , self . ext_mul_calls, ) ;
418+ }
419+ }
264420}
265421
266422#[ cfg( test) ]
0 commit comments