Skip to content

Commit 6e7496e

Browse files
committed
vm core: implement execute_computation
1 parent 5e8a8c6 commit 6e7496e

File tree

5 files changed

+213
-26
lines changed

5 files changed

+213
-26
lines changed

crates/leanVm/src/context/run_context.rs

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ impl RunContext {
3636
///
3737
/// - If the operand is a constant, it returns the constant.
3838
/// - If it's a memory location, it computes the address relative to `fp` and fetches the value from memory.
39-
pub fn get_value_from_mem_or_constant<F>(
39+
pub fn value_from_mem_or_constant<F>(
4040
&self,
4141
operand: &MemOrConstant<F>,
4242
memory: &MemoryManager,
43-
) -> Result<MemoryValue<F>, VirtualMachineError<F>>
43+
) -> Result<MemoryValue<F>, MemoryError<F>>
4444
where
4545
F: PrimeField64,
4646
{
@@ -50,7 +50,7 @@ impl RunContext {
5050
let addr = self.fp.add_usize(*shift)?;
5151
memory
5252
.get(addr)
53-
.ok_or_else(|| MemoryError::UninitializedMemory(addr).into())
53+
.ok_or(MemoryError::UninitializedMemory(addr))
5454
}
5555
}
5656
}
@@ -59,11 +59,11 @@ impl RunContext {
5959
///
6060
/// - If the operand is the frame pointer `Fp`, it returns the `fp` address itself.
6161
/// - If it's a memory location, it computes the address relative to `fp` and fetches the value.
62-
pub fn get_value_from_mem_or_fp<F>(
62+
pub fn value_from_mem_or_fp<F>(
6363
&self,
6464
operand: &MemOrFp,
6565
memory: &MemoryManager,
66-
) -> Result<MemoryValue<F>, VirtualMachineError<F>>
66+
) -> Result<MemoryValue<F>, MemoryError<F>>
6767
where
6868
F: PrimeField64,
6969
{
@@ -73,7 +73,7 @@ impl RunContext {
7373
let addr = self.fp.add_usize(*shift)?;
7474
memory
7575
.get(addr)
76-
.ok_or_else(|| MemoryError::UninitializedMemory(addr).into())
76+
.ok_or(MemoryError::UninitializedMemory(addr))
7777
}
7878
}
7979
}
@@ -84,7 +84,7 @@ impl RunContext {
8484
/// - a constant value,
8585
/// - a memory location relative to `fp`,
8686
/// - the `fp` register itself.
87-
pub fn get_value_from_mem_or_fp_or_constant<F>(
87+
pub fn value_from_mem_or_fp_or_constant<F>(
8888
&self,
8989
operand: &MemOrFpOrConstant<F>,
9090
memory: &MemoryManager,
@@ -131,13 +131,11 @@ mod tests {
131131
// A constant operand with field element 42.
132132
let operand = MemOrConstant::Constant(F::from_u64(42));
133133

134-
// Run `get_value_from_mem_or_constant` with an unused memory manager (memory is not needed for constants).
134+
// Run `value_from_mem_or_constant` with an unused memory manager (memory is not needed for constants).
135135
let memory = MemoryManager::default();
136136

137137
// It should return the wrapped constant as a MemoryValue::Int.
138-
let result = ctx
139-
.get_value_from_mem_or_constant(&operand, &memory)
140-
.unwrap();
138+
let result = ctx.value_from_mem_or_constant(&operand, &memory).unwrap();
141139
assert_eq!(result, MemoryValue::Int(F::from_u64(42)));
142140
}
143141

@@ -173,10 +171,8 @@ mod tests {
173171
// The operand asks to read memory at fp + 2.
174172
let operand = MemOrConstant::MemoryAfterFp { shift: 2 };
175173

176-
// Call get_value_from_mem_or_constant, which should fetch the value we inserted.
177-
let result = ctx
178-
.get_value_from_mem_or_constant(&operand, &memory)
179-
.unwrap();
174+
// Call value_from_mem_or_constant, which should fetch the value we inserted.
175+
let result = ctx.value_from_mem_or_constant(&operand, &memory).unwrap();
180176
assert_eq!(result, expected_val);
181177
}
182178

@@ -201,13 +197,13 @@ mod tests {
201197
fp,
202198
);
203199

204-
// Calling get_value_from_mem_or_constant should return a VirtualMachineError::MemoryError::UninitializedMemory.
200+
// Calling value_from_mem_or_constant should return a VirtualMachineError::MemoryError::UninitializedMemory.
205201
let err = ctx
206-
.get_value_from_mem_or_constant(&operand, &memory)
202+
.value_from_mem_or_constant(&operand, &memory)
207203
.unwrap_err();
208204

209205
match err {
210-
VirtualMachineError::Memory(MemoryError::UninitializedMemory(addr)) => {
206+
MemoryError::UninitializedMemory(addr) => {
211207
assert_eq!(addr.segment_index, fp.segment_index);
212208
assert_eq!(addr.offset, fp.offset + 1);
213209
}
@@ -221,7 +217,7 @@ mod tests {
221217
let operand = MemOrFpOrConstant::Constant(F::from_u64(123));
222218
let memory = MemoryManager::default();
223219
let result = ctx
224-
.get_value_from_mem_or_fp_or_constant(&operand, &memory)
220+
.value_from_mem_or_fp_or_constant(&operand, &memory)
225221
.unwrap();
226222
assert_eq!(result, MemoryValue::Int(F::from_u64(123)));
227223
}
@@ -233,7 +229,7 @@ mod tests {
233229
let operand = MemOrFpOrConstant::<F>::Fp;
234230
let memory = MemoryManager::default();
235231
let result = ctx
236-
.get_value_from_mem_or_fp_or_constant(&operand, &memory)
232+
.value_from_mem_or_fp_or_constant(&operand, &memory)
237233
.unwrap();
238234
assert_eq!(result, MemoryValue::Address(fp_addr));
239235
}
@@ -252,7 +248,7 @@ mod tests {
252248
let ctx = RunContext::new(MemoryAddress::new(0, 0), fp);
253249
let operand = MemOrFpOrConstant::MemoryAfterFp { shift: 7 };
254250
let result = ctx
255-
.get_value_from_mem_or_fp_or_constant(&operand, &memory)
251+
.value_from_mem_or_fp_or_constant(&operand, &memory)
256252
.unwrap();
257253
assert_eq!(result, expected_val);
258254
}

crates/leanVm/src/core.rs

Lines changed: 174 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use p3_field::PrimeField64;
22

33
use crate::{
4-
bytecode::{instruction::Instruction, operand::MemOrFp},
4+
bytecode::{
5+
instruction::Instruction,
6+
operand::{MemOrConstant, MemOrFp, MemOrFpOrConstant},
7+
operation::Operation,
8+
},
59
context::run_context::RunContext,
6-
errors::{memory::MemoryError, vm::VirtualMachineError},
7-
memory::manager::MemoryManager,
10+
errors::{math::MathError, memory::MemoryError, vm::VirtualMachineError},
11+
memory::{manager::MemoryManager, val::MemoryValue},
812
};
913

1014
#[derive(Debug, Default)]
@@ -43,7 +47,7 @@ impl VirtualMachine {
4347
// This will return an error if the memory location is uninitialized.
4448
let condition_val = self
4549
.run_context
46-
.get_value_from_mem_or_constant(condition, &self.memory_manager)?;
50+
.value_from_mem_or_constant(condition, &self.memory_manager)?;
4751

4852
// A jump condition must be a field element.
4953
let is_zero = condition_val.to_f()?.is_zero();
@@ -57,7 +61,7 @@ impl VirtualMachine {
5761
// First, resolve the `dest` operand to get the target address value.
5862
let dest_val = self
5963
.run_context
60-
.get_value_from_mem_or_constant(dest, &self.memory_manager)?;
64+
.value_from_mem_or_constant(dest, &self.memory_manager)?;
6165

6266
// The resolved destination value must be a valid address.
6367
//
@@ -123,6 +127,171 @@ impl VirtualMachine {
123127

124128
Ok(())
125129
}
130+
131+
/// Executes a single instruction, forming one step of the VM's execution cycle.
132+
///
133+
/// This function is the engine of the virtual machine. It orchestrates the two main phases
134+
/// of a single step: execution and register update.
135+
///
136+
/// 1. **Execution:** It first matches on the `instruction` variant to dispatch to the appropriate
137+
/// helper method. These helpers are responsible for fetching operands, performing the instruction's core logic, and
138+
/// verifying any required assertions (e.g., that a computed value matches an expected one).
139+
///
140+
/// 2. **Register Update:** If the execution phase completes successfully, this function then
141+
/// calls `update_registers` to advance the program counter (`pc`) and frame pointer (`fp`)
142+
/// to prepare for the next instruction.
143+
pub fn run_instruction<F>(
144+
&mut self,
145+
instruction: &Instruction<F>,
146+
) -> Result<(), VirtualMachineError<F>>
147+
where
148+
F: PrimeField64,
149+
{
150+
// Dispatch to the appropriate execution logic based on the instruction type.
151+
match instruction {
152+
// Handle arithmetic operations like ADD and MUL.
153+
Instruction::Computation {
154+
operation,
155+
arg_a,
156+
arg_b,
157+
res,
158+
} => self.execute_computation(operation, arg_a, arg_b, res)?,
159+
160+
// Handle double-dereference memory operations.
161+
Instruction::Deref {
162+
shift_0,
163+
shift_1,
164+
res,
165+
} => self.execute_deref(*shift_0, *shift_1, res)?,
166+
167+
// The `JumpIfNotZero` instruction has no execution logic; its effects
168+
// (changing pc and fp) are handled entirely within the register update phase.
169+
Instruction::JumpIfNotZero { .. } => {}
170+
171+
// Handle the Poseidon2 (16-element) precompile.
172+
Instruction::Poseidon2_16 { shift } => self.execute_poseidon2_16(*shift)?,
173+
174+
// Handle the Poseidon2 (24-element) precompile.
175+
Instruction::Poseidon2_24 { shift } => self.execute_poseidon2_24(*shift)?,
176+
177+
// Handle the extension field multiplication precompile.
178+
Instruction::ExtensionMul { args } => self.execute_extension_mul(*args)?,
179+
}
180+
181+
// After the instruction's core logic has been successfully executed,
182+
// update the pc and fp registers to prepare for the next cycle.
183+
self.update_registers(instruction)
184+
}
185+
186+
/// Executes a computation instruction (`res = arg_a op arg_b`), handling deduction and assertion.
187+
///
188+
/// This function implements the core logic for `ADD` and `MUL` instructions. It follows
189+
/// a "constraint satisfaction" model:
190+
///
191+
/// 1. **Deduction:** If exactly one of the three operands (`res`, `arg_a`, `arg_b`) is unknown
192+
/// (i.e., its memory cell is uninitialized), the function computes its value from the other
193+
/// two and writes it to memory.
194+
/// 2. **Assertion:** If all three operands are already known, the function computes `arg_a op arg_b`
195+
/// and asserts that it equals the value of `res`.
196+
fn execute_computation<F>(
197+
&mut self,
198+
operation: &Operation,
199+
arg_a: &MemOrConstant<F>,
200+
arg_b: &MemOrFp,
201+
res: &MemOrConstant<F>,
202+
) -> Result<(), VirtualMachineError<F>>
203+
where
204+
F: PrimeField64,
205+
{
206+
let memory_manager = &self.memory_manager;
207+
let run_ctx = &self.run_context;
208+
209+
let val_a = run_ctx.value_from_mem_or_constant(arg_a, memory_manager);
210+
let val_b = run_ctx.value_from_mem_or_fp(arg_b, memory_manager);
211+
let val_res = run_ctx.value_from_mem_or_constant(res, memory_manager);
212+
213+
match (val_a, val_b, val_res) {
214+
// Case 1: a OP b = c — compute c
215+
(Ok(MemoryValue::Int(a)), Ok(MemoryValue::Int(b)), Ok(MemoryValue::Address(addr))) => {
216+
let c = operation.compute(a, b);
217+
self.memory_manager.memory.insert(addr, c)?;
218+
}
219+
// Case 2: a OP b = c — recover b
220+
(Ok(MemoryValue::Int(a)), Ok(MemoryValue::Address(addr)), Ok(MemoryValue::Int(c))) => {
221+
let b = operation
222+
.inverse_compute(c, a)
223+
.ok_or(MathError::DivisionByZero)?;
224+
self.memory_manager.memory.insert(addr, b)?;
225+
}
226+
// Case 3: a OP b = c — recover a
227+
(Ok(MemoryValue::Address(addr)), Ok(MemoryValue::Int(b)), Ok(MemoryValue::Int(c))) => {
228+
let a = operation
229+
.inverse_compute(c, b)
230+
.ok_or(MathError::DivisionByZero)?;
231+
self.memory_manager.memory.insert(addr, a)?;
232+
}
233+
// Case 4: a OP b = c — assert equality
234+
(Ok(MemoryValue::Int(a)), Ok(MemoryValue::Int(b)), Ok(MemoryValue::Int(c))) => {
235+
let computed = operation.compute(a, b);
236+
if computed != c {
237+
return Err(VirtualMachineError::AssertEqFailed {
238+
computed: computed.into(),
239+
expected: c.into(),
240+
});
241+
}
242+
}
243+
_ => return Err(VirtualMachineError::TooManyUnknownOperands),
244+
}
245+
246+
Ok(())
247+
}
248+
249+
/// Executes a double-dereference instruction (`res = m[m[fp + shift_0] + shift_1]`) and asserts the result.
250+
///
251+
/// This function handles instructions that require reading a pointer from one memory
252+
/// location to access a value at another.
253+
///
254+
/// # Errors
255+
/// This function will return an `Err` if:
256+
/// - Any memory access targets an uninitialized memory cell.
257+
/// - The first memory access at `m[fp + shift_0]` does not yield a valid `MemoryAddress`.
258+
/// - The final, dereferenced value does not match the expected value specified by `res`.
259+
fn execute_deref<F>(
260+
&self,
261+
_shift_0: usize,
262+
_shift_1: usize,
263+
_res: &MemOrFpOrConstant<F>,
264+
) -> Result<(), VirtualMachineError<F>>
265+
where
266+
F: PrimeField64,
267+
{
268+
// TODO: implement this instruction.
269+
Ok(())
270+
}
271+
272+
fn execute_poseidon2_16<F>(&self, _shift: usize) -> Result<(), VirtualMachineError<F>>
273+
where
274+
F: PrimeField64,
275+
{
276+
// TODO: implement this instruction.
277+
Ok(())
278+
}
279+
280+
fn execute_poseidon2_24<F>(&self, _shift: usize) -> Result<(), VirtualMachineError<F>>
281+
where
282+
F: PrimeField64,
283+
{
284+
// TODO: implement this instruction.
285+
Ok(())
286+
}
287+
288+
fn execute_extension_mul<F>(&self, _args: [usize; 3]) -> Result<(), VirtualMachineError<F>>
289+
where
290+
F: PrimeField64,
291+
{
292+
// TODO: implement this instruction.
293+
Ok(())
294+
}
126295
}
127296

128297
#[cfg(test)]

crates/leanVm/src/errors/math.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ where
1212
MemoryAddressAddUsizeOffsetExceeded(Box<(MemoryAddress, usize)>),
1313
#[error("Operation failed: {} + {}, maximum offset value exceeded", 0.0, 0.1)]
1414
MemoryAddressAddFieldOffsetExceeded(Box<(MemoryAddress, F)>),
15+
#[error("Division by zero")]
16+
DivisionByZero,
1517
}

crates/leanVm/src/errors/vm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ where
2424
computed: MemoryValue<F>,
2525
expected: MemoryValue<F>,
2626
},
27+
#[error("Too many unknown operands.")]
28+
TooManyUnknownOperands,
2729
}

crates/leanVm/src/memory/val.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ where
7373
}
7474
}
7575

76+
impl<F> From<MemoryAddress> for MemoryValue<F>
77+
where
78+
F: PrimeField64,
79+
{
80+
fn from(addr: MemoryAddress) -> Self {
81+
Self::Address(addr)
82+
}
83+
}
84+
85+
impl<F> From<F> for MemoryValue<F>
86+
where
87+
F: PrimeField64,
88+
{
89+
fn from(f: F) -> Self {
90+
Self::Int(f)
91+
}
92+
}
93+
7694
#[cfg(test)]
7795
impl<F> Arbitrary for MemoryValue<F>
7896
where

0 commit comments

Comments
 (0)