Skip to content

Commit a94d5d4

Browse files
authored
vm core: update context (#5)
* vm core: update context * small fix
1 parent ae5af2c commit a94d5d4

File tree

9 files changed

+401
-5
lines changed

9 files changed

+401
-5
lines changed

crates/leanVm/src/context/run_context.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
use crate::memory::address::MemoryAddress;
1+
use p3_field::PrimeField64;
2+
3+
use crate::{
4+
errors::{memory::MemoryError, vm::VirtualMachineError},
5+
memory::{address::MemoryAddress, manager::MemoryManager, val::MemoryValue},
6+
types::instruction::MemOrConstant,
7+
};
28

39
#[derive(Debug, Default)]
410
pub struct RunContext {
@@ -25,4 +31,132 @@ impl RunContext {
2531
pub const fn fp(&self) -> &MemoryAddress {
2632
&self.fp
2733
}
34+
35+
/// Resolves a `MemOrConstant` operand to its final value.
36+
///
37+
/// - If the operand is a constant, it returns the constant.
38+
/// - If it's a memory location, it computes the address relative to `fp` and fetches the value from memory.
39+
pub fn get_value<F>(
40+
&self,
41+
operand: &MemOrConstant<F>,
42+
memory: &MemoryManager,
43+
) -> Result<MemoryValue<F>, VirtualMachineError<F>>
44+
where
45+
F: PrimeField64,
46+
{
47+
match operand {
48+
MemOrConstant::Constant(val) => Ok(MemoryValue::Int(*val)),
49+
MemOrConstant::MemoryAfterFp { shift } => {
50+
let addr = (self.fp + *shift)?;
51+
memory
52+
.get(addr)
53+
.ok_or_else(|| MemoryError::UninitializedMemory(addr).into())
54+
}
55+
}
56+
}
57+
}
58+
59+
#[cfg(test)]
60+
mod tests {
61+
use p3_baby_bear::BabyBear;
62+
use p3_field::PrimeCharacteristicRing;
63+
64+
use super::*;
65+
66+
type F = BabyBear;
67+
68+
#[test]
69+
fn test_get_value_constant() {
70+
// Create a dummy RunContext with pc and fp.
71+
let ctx = RunContext::new(
72+
MemoryAddress {
73+
segment_index: 0,
74+
offset: 0,
75+
},
76+
MemoryAddress {
77+
segment_index: 1,
78+
offset: 0,
79+
},
80+
);
81+
82+
// A constant operand with field element 42.
83+
let operand = MemOrConstant::Constant(F::from_u64(42));
84+
85+
// Run `get_value` with an unused memory manager (memory is not needed for constants).
86+
let memory = MemoryManager::default();
87+
88+
// It should return the wrapped constant as a MemoryValue::Int.
89+
let result = ctx.get_value(&operand, &memory).unwrap();
90+
assert_eq!(result, MemoryValue::Int(F::from_u64(42)));
91+
}
92+
93+
#[test]
94+
fn test_get_value_memory_after_fp_success() {
95+
let mut memory = MemoryManager::default();
96+
97+
// Add a segment that will be used for `fp`.
98+
let fp = memory.add(); // segment_index = 0, offset = 0
99+
100+
// Shift = 2, so address to read is fp + 2 => offset 2 in the same segment.
101+
let addr_to_read = MemoryAddress {
102+
segment_index: fp.segment_index,
103+
offset: fp.offset + 2,
104+
};
105+
106+
// Insert a value at that address manually.
107+
let expected_val = MemoryValue::Int(F::from_u64(99));
108+
memory
109+
.memory
110+
.insert(addr_to_read, expected_val.clone())
111+
.unwrap();
112+
113+
// Create a RunContext with that fp.
114+
let ctx = RunContext::new(
115+
MemoryAddress {
116+
segment_index: 0,
117+
offset: 0,
118+
}, // dummy pc
119+
fp,
120+
);
121+
122+
// The operand asks to read memory at fp + 2.
123+
let operand = MemOrConstant::MemoryAfterFp { shift: 2 };
124+
125+
// Call get_value, which should fetch the value we inserted.
126+
let result = ctx.get_value(&operand, &memory).unwrap();
127+
assert_eq!(result, expected_val);
128+
}
129+
130+
#[test]
131+
fn test_get_value_memory_after_fp_uninitialized_memory() {
132+
let mut memory = MemoryManager::default();
133+
134+
// Create a segment and set fp to its base.
135+
let fp = memory.add(); // segment_index = 0, offset = 0
136+
137+
// We won't insert anything, so all memory is uninitialized.
138+
139+
// Shift = 1 → fp + 1 points to offset 1 (which is uninitialized).
140+
let operand: MemOrConstant<F> = MemOrConstant::MemoryAfterFp { shift: 1 };
141+
142+
// Set up context.
143+
let ctx = RunContext::new(
144+
MemoryAddress {
145+
segment_index: 0,
146+
offset: 0,
147+
}, // dummy pc
148+
fp,
149+
);
150+
151+
// Calling get_value should return a VirtualMachineError::MemoryError::UninitializedMemory.
152+
let err = ctx.get_value(&operand, &memory).unwrap_err();
153+
154+
match err {
155+
VirtualMachineError::Memory(MemoryError::UninitializedMemory(addr)) => {
156+
assert_eq!(addr.segment_index, fp.segment_index);
157+
assert_eq!(addr.offset, fp.offset + 1);
158+
}
159+
other => panic!("Unexpected error: {other:?}"),
160+
}
161+
}
28162
}

crates/leanVm/src/core.rs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,108 @@
1-
use crate::{context::run_context::RunContext, memory::manager::MemoryManager};
1+
use p3_field::PrimeField64;
2+
3+
use crate::{
4+
context::run_context::RunContext,
5+
errors::{memory::MemoryError, vm::VirtualMachineError},
6+
memory::manager::MemoryManager,
7+
types::instruction::{Instruction, MemOrFp},
8+
};
29

310
#[derive(Debug, Default)]
411
pub struct VirtualMachine {
512
pub(crate) run_context: RunContext,
613
pub memory_manager: MemoryManager,
714
}
15+
16+
impl VirtualMachine {
17+
/// Advances the program counter (`pc`) to the next instruction.
18+
///
19+
/// This function embodies the control flow logic of the zkVM. For most instructions,
20+
/// it performs a regular increment of the **`pc`**. However, for the `JumpIfNotZero`
21+
/// instruction (`JUZ`), it implements conditional branching.
22+
///
23+
/// ### `JumpIfNotZero` Logic
24+
///
25+
/// When a `JumpIfNotZero` instruction is processed:
26+
/// 1. The `condition` operand is resolved to a field element.
27+
/// 2. If this value is **zero**, the program continues sequentially, and the **`pc`** is incremented by 1.
28+
/// 3. If the value is **non-zero**, a jump is executed. The `dest` operand is resolved to find the
29+
/// target `MemoryAddress`, which then becomes the new **`pc`**.
30+
pub fn update_pc<F>(
31+
&mut self,
32+
instruction: &Instruction<F>,
33+
) -> Result<(), VirtualMachineError<F>>
34+
where
35+
F: PrimeField64,
36+
{
37+
// Determine the next program counter `pc` by checking if the instruction is a conditional jump.
38+
let next_pc = if let Instruction::JumpIfNotZero {
39+
condition, dest, ..
40+
} = instruction
41+
{
42+
// For a `JumpIfNotZero` instruction, resolve the `condition` operand from memory or constants.
43+
// This will return an error if the memory location is uninitialized.
44+
let condition_val = self
45+
.run_context
46+
.get_value(condition, &self.memory_manager)?;
47+
48+
// A jump condition must be a field element.
49+
let is_zero = condition_val.to_f()?.is_zero();
50+
51+
if is_zero {
52+
// **Condition is zero**: The jump is not taken. Advance the `pc` by one.
53+
(*self.run_context.pc() + 1)?
54+
} else {
55+
// **Condition is non-zero**: Execute the jump.
56+
//
57+
// First, resolve the `dest` operand to get the target address value.
58+
let dest_val = self.run_context.get_value(dest, &self.memory_manager)?;
59+
60+
// The resolved destination value must be a valid address.
61+
//
62+
// Convert it and set it as the new `pc`.
63+
dest_val.try_into()?
64+
}
65+
} else {
66+
// For any instruction other than `JumpIfNotZero`, advance the `pc` by one.
67+
(*self.run_context.pc() + 1)?
68+
};
69+
70+
// Update the virtual machine's program counter with the calculated next address.
71+
self.run_context.pc = next_pc;
72+
Ok(())
73+
}
74+
75+
/// Updates the frame pointer (`fp`) based on the executed instruction.
76+
pub fn update_fp<F>(
77+
&mut self,
78+
instruction: &Instruction<F>,
79+
) -> Result<(), VirtualMachineError<F>>
80+
where
81+
F: PrimeField64,
82+
{
83+
if let Instruction::JumpIfNotZero { updated_fp, .. } = instruction {
84+
let new_fp = match updated_fp {
85+
// The instruction specifies keeping the same `fp`.
86+
MemOrFp::Fp => self.run_context.fp,
87+
// The instruction specifies updating `fp` to a value from memory.
88+
MemOrFp::MemoryAfterFp { shift } => {
89+
let addr = (*self.run_context.fp() + *shift)?;
90+
let value = self
91+
.memory_manager
92+
.get(addr)
93+
.ok_or(MemoryError::UninitializedMemory(addr))?;
94+
95+
// The fetched value must be a valid memory address to become the new `fp`.
96+
value.try_into()?
97+
}
98+
};
99+
self.run_context.fp = new_fp;
100+
}
101+
102+
// For the other instructions, we do nothing for now.
103+
//
104+
// To be checked in the future.
105+
106+
Ok(())
107+
}
108+
}

crates/leanVm/src/errors/memory.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,17 @@ where
3636
/// Error related to mathematical operations.
3737
#[error(transparent)]
3838
Math(#[from] MathError),
39+
40+
/// Error when a memory value is expected to be an integer, but it is an address to another memory location.
41+
#[error("Memory value should be an integer.")]
42+
ValueNotInteger,
43+
44+
#[error("Memory at address {0:?} is uninitialized.")]
45+
UninitializedMemory(MemoryAddress),
46+
47+
#[error("Memory address is expected but we got an integer.")]
48+
ExpectedMemoryAddress,
49+
50+
#[error("Inteer is expected but we got a memory address.")]
51+
ExpectedInteger,
3952
}

crates/leanVm/src/errors/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod math;
22
pub mod memory;
3+
pub mod vm;

crates/leanVm/src/errors/vm.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use std::fmt::Debug;
2+
3+
use thiserror::Error;
4+
5+
use super::{math::MathError, memory::MemoryError};
6+
7+
#[derive(Debug, Error)]
8+
pub enum VirtualMachineError<F>
9+
where
10+
F: Debug,
11+
{
12+
#[error(transparent)]
13+
Memory(#[from] MemoryError<F>),
14+
#[error(transparent)]
15+
Math(#[from] MathError),
16+
}

crates/leanVm/src/memory/address.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use std::{fmt::Display, ops::Add};
22

3+
use p3_field::PrimeField64;
34
#[cfg(test)]
45
use proptest::prelude::*;
56

6-
use crate::errors::math::MathError;
7+
use super::val::MemoryValue;
8+
use crate::errors::{math::MathError, memory::MemoryError};
79

810
#[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone, Copy, Debug, Default)]
911
pub struct MemoryAddress {
@@ -36,6 +38,20 @@ impl Display for MemoryAddress {
3638
}
3739
}
3840

41+
impl<F> TryFrom<MemoryValue<F>> for MemoryAddress
42+
where
43+
F: PrimeField64,
44+
{
45+
type Error = MemoryError<F>;
46+
47+
fn try_from(value: MemoryValue<F>) -> Result<Self, Self::Error> {
48+
match value {
49+
MemoryValue::Address(addr) => Ok(addr),
50+
MemoryValue::Int(_) => Err(MemoryError::ExpectedMemoryAddress),
51+
}
52+
}
53+
}
54+
3955
#[cfg(test)]
4056
impl Arbitrary for MemoryAddress {
4157
type Parameters = ();
@@ -58,10 +74,14 @@ impl Arbitrary for MemoryAddress {
5874

5975
#[cfg(test)]
6076
mod tests {
77+
use p3_baby_bear::BabyBear;
78+
use p3_field::PrimeCharacteristicRing;
6179
use proptest::prelude::*;
6280

6381
use super::*;
6482

83+
type F = BabyBear;
84+
6585
#[test]
6686
fn test_add_usize_success() {
6787
let addr = MemoryAddress {
@@ -124,4 +144,43 @@ mod tests {
124144
}
125145
}
126146
}
147+
148+
#[test]
149+
fn test_try_into_memory_address_ok() {
150+
// Construct a MemoryAddress.
151+
let addr = MemoryAddress {
152+
segment_index: 3,
153+
offset: 42,
154+
};
155+
156+
// Wrap it in a MemoryValue::Address variant
157+
let val: MemoryValue<F> = MemoryValue::Address(addr);
158+
159+
// Try converting it into a MemoryAddress
160+
let result: Result<MemoryAddress, MemoryError<F>> = val.try_into();
161+
162+
// Assert it succeeds
163+
assert!(result.is_ok());
164+
165+
// Assert the returned address is equal to the original
166+
assert_eq!(result.unwrap(), addr);
167+
}
168+
169+
#[test]
170+
fn test_try_into_memory_address_err_on_int() {
171+
// Create an integer value
172+
let field_elem = F::from_u64(17);
173+
174+
// Wrap it in a MemoryValue::Int variant
175+
let val: MemoryValue<F> = MemoryValue::Int(field_elem);
176+
177+
// Try converting it into a MemoryAddress
178+
let result: Result<MemoryAddress, MemoryError<F>> = val.try_into();
179+
180+
// Assert it fails
181+
assert!(result.is_err());
182+
183+
// Assert the specific error is ExpectedMemoryAddress
184+
assert_eq!(result.unwrap_err(), MemoryError::ExpectedMemoryAddress);
185+
}
127186
}

0 commit comments

Comments
 (0)