Skip to content

Commit d657307

Browse files
authored
vm core: implement main run (#9)
1 parent 794e7e1 commit d657307

File tree

7 files changed

+197
-7
lines changed

7 files changed

+197
-7
lines changed

crates/leanVm/src/bytecode/hint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl Hint {
5454
///
5555
/// These are not part of the trace or AIR and are only used by the prover for state setup or inspection.
5656
/// The verifier does not need to observe these effects.
57-
fn execute(
57+
pub fn execute(
5858
&self,
5959
memory_manager: &mut MemoryManager,
6060
run_context: &mut RunContext,

crates/leanVm/src/bytecode/program.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use super::Bytecode;
2-
use crate::constant::F;
2+
use crate::{
3+
constant::F,
4+
memory::{address::MemoryAddress, manager::MemoryManager},
5+
};
36

47
/// Represents a program to be executed by the zkVM.
58
#[derive(Debug, Clone, Default)]
@@ -11,3 +14,29 @@ pub struct Program {
1114
/// The private (witness) inputs for this specific execution.
1215
pub private_input: Vec<F>,
1316
}
17+
18+
/// Represents the results of a successful program execution.
19+
#[derive(Debug)]
20+
pub struct ExecutionResult {
21+
/// The final state of the memory manager.
22+
pub memory_manager: MemoryManager,
23+
/// The execution trace of the program counter (pc) values.
24+
///
25+
/// TODO: in the future, not sure we need this.
26+
pub pcs: Vec<MemoryAddress>,
27+
/// The execution trace of the frame pointer (fp) values.
28+
///
29+
/// TODO: in the future, not sure we need this.
30+
pub fps: Vec<MemoryAddress>,
31+
}
32+
33+
/// An helper struct to hold the results of a single execution pass.
34+
#[derive(Debug)]
35+
pub struct ExecutionPassResult {
36+
/// The result of the execution.
37+
pub result: ExecutionResult,
38+
/// The initial allocation pointers.
39+
pub initial_ap: MemoryAddress,
40+
/// The initial allocation pointers for vectorized memory.
41+
pub initial_ap_vec: MemoryAddress,
42+
}

crates/leanVm/src/constant.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ pub const POSEIDON_24_NULL_HASH_PTR: MemoryAddress = MemoryAddress::new(VEC_RUNT
3434

3535
/// Start of the public input memory region within the PUBLIC_DATA_SEGMENT.
3636
pub const PUBLIC_INPUT_START: MemoryAddress = MemoryAddress::new(PUBLIC_DATA_SEGMENT, 0);
37+
38+
/// The maximum size of the memory.
39+
pub const MAX_MEMORY_SIZE: usize = 1 << 23;

crates/leanVm/src/core.rs

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2837
impl<Perm16, Perm24> VirtualMachine<Perm16, Perm24>
@@ -37,6 +46,14 @@ where
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+
"\nBytecode 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)]

crates/leanVm/src/errors/vm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ pub enum VirtualMachineError {
2222
},
2323
#[error("Too many unknown operands.")]
2424
TooManyUnknownOperands,
25+
#[error("Program counter (pc) is out of bounds.")]
26+
PCOutOfBounds,
2527
}

crates/leanVm/src/memory/manager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::{address::MemoryAddress, mem::Memory, val::MemoryValue};
22
use crate::errors::memory::MemoryError;
33

44
/// A high level manager for the memory.
5-
#[derive(Debug, Default)]
5+
#[derive(Debug, Default, Clone)]
66
pub struct MemoryManager {
77
pub memory: Memory,
88
}

crates/leanVm/src/memory/mem.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::mem::MaybeUninit;
33
use super::{address::MemoryAddress, cell::MemoryCell, val::MemoryValue};
44
use crate::errors::memory::MemoryError;
55

6-
#[derive(Debug, Default)]
6+
#[derive(Debug, Default, Clone)]
77
pub struct Memory {
88
pub(crate) data: Vec<Vec<MemoryCell>>,
99
}

0 commit comments

Comments
 (0)