Skip to content

Commit f5ef3f5

Browse files
committed
vm core: unit tests for run context update
1 parent a94d5d4 commit f5ef3f5

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed

crates/leanVm/src/core.rs

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,209 @@ impl VirtualMachine {
106106
Ok(())
107107
}
108108
}
109+
110+
#[cfg(test)]
111+
mod tests {
112+
use p3_baby_bear::BabyBear;
113+
use p3_field::PrimeCharacteristicRing;
114+
115+
use super::*;
116+
use crate::{
117+
memory::{address::MemoryAddress, val::MemoryValue},
118+
types::instruction::{MemOrConstant, Operation},
119+
};
120+
121+
type F = BabyBear;
122+
123+
/// Creates and configures a `VirtualMachine` instance for testing purposes.
124+
///
125+
/// This function streamlines the setup process for tests by initializing the VM
126+
/// with a specific state, including
127+
/// - the program counter (`pc`),
128+
/// - the frame pointer (`fp`),
129+
/// - any required initial memory values.
130+
fn setup_vm(
131+
pc: MemoryAddress,
132+
fp: MemoryAddress,
133+
initial_memory: &[(MemoryAddress, MemoryValue<F>)],
134+
) -> VirtualMachine {
135+
// Create a new VM with default values.
136+
let mut vm = VirtualMachine::default();
137+
// Set the initial program counter and frame pointer.
138+
vm.run_context.pc = pc;
139+
vm.run_context.fp = fp;
140+
// Iterate through the provided initial memory layout.
141+
for (addr, val) in initial_memory {
142+
// Ensure enough memory segments are allocated to accommodate the address.
143+
while vm.memory_manager.num_segments() <= addr.segment_index {
144+
vm.memory_manager.add();
145+
}
146+
// Insert the value at the specified address, panicking on failure for test simplicity.
147+
vm.memory_manager.memory.insert(*addr, val.clone()).unwrap();
148+
}
149+
// Return the fully configured VM.
150+
vm
151+
}
152+
153+
#[test]
154+
fn test_update_pc_for_non_jnz_instruction() {
155+
// Setup: Initialize a VM with the PC at (0, 5).
156+
let mut vm = setup_vm(MemoryAddress::new(0, 5), MemoryAddress::new(1, 0), &[]);
157+
// Define a non-jump instruction (e.g., a computation).
158+
let instruction = Instruction::Computation::<F> {
159+
operation: Operation::Add,
160+
arg_a: MemOrConstant::Constant(F::ONE),
161+
arg_b: MemOrFp::Fp,
162+
res: MemOrConstant::MemoryAfterFp { shift: 0 },
163+
};
164+
// Execute: Update the PC based on this instruction.
165+
vm.update_pc(&instruction).unwrap();
166+
// Verify: The PC should increment by 1, as it's not a JNZ instruction.
167+
assert_eq!(vm.run_context.pc, MemoryAddress::new(0, 6));
168+
}
169+
170+
#[test]
171+
fn test_update_pc_jnz_condition_zero() {
172+
// Setup: Initialize PC and FP registers.
173+
let pc = MemoryAddress::new(0, 10);
174+
let fp = MemoryAddress::new(1, 5);
175+
// Pre-load memory with a zero value at the address `fp + 1`, which will be our condition.
176+
let mut vm = setup_vm(pc, fp, &[((fp + 1).unwrap(), MemoryValue::Int(F::ZERO))]);
177+
// Define a JNZ instruction where the condition points to the zero value.
178+
let instruction = Instruction::JumpIfNotZero::<F> {
179+
condition: MemOrConstant::MemoryAfterFp { shift: 1 },
180+
dest: MemOrConstant::Constant(F::from_u64(99)), // This destination should be ignored.
181+
updated_fp: MemOrFp::Fp,
182+
};
183+
// Execute: Update the PC.
184+
vm.update_pc(&instruction).unwrap();
185+
// Verify: Since the condition is zero, the jump is not taken, and the PC increments by 1.
186+
assert_eq!(vm.run_context.pc, MemoryAddress::new(0, 11));
187+
}
188+
189+
#[test]
190+
fn test_update_pc_jnz_condition_nonzero_jumps() {
191+
// Setup: Initialize PC and FP registers.
192+
let pc = MemoryAddress::new(0, 10);
193+
let fp = MemoryAddress::new(1, 5);
194+
// Define the target address for the jump.
195+
let jump_target = MemoryAddress::new(2, 20);
196+
// Pre-load memory with a non-zero condition value and the jump target address.
197+
let mut vm = setup_vm(
198+
pc,
199+
fp,
200+
&[
201+
// The condition value (non-zero).
202+
((fp + 1).unwrap(), MemoryValue::Int(F::from_u64(42))),
203+
// The destination address for the jump.
204+
((fp + 2).unwrap(), MemoryValue::Address(jump_target)),
205+
],
206+
);
207+
// Define a JNZ instruction pointing to the condition and destination.
208+
let instruction = Instruction::JumpIfNotZero::<F> {
209+
condition: MemOrConstant::MemoryAfterFp { shift: 1 },
210+
dest: MemOrConstant::MemoryAfterFp { shift: 2 },
211+
updated_fp: MemOrFp::Fp,
212+
};
213+
// Execute: Update the PC.
214+
vm.update_pc(&instruction).unwrap();
215+
// Verify: Since the condition is non-zero, the PC should be updated to the jump target.
216+
assert_eq!(vm.run_context.pc, jump_target);
217+
}
218+
219+
#[test]
220+
fn test_update_pc_jnz_condition_is_address_fails() {
221+
// Setup: Initialize PC and FP.
222+
let pc = MemoryAddress::new(0, 10);
223+
let fp = MemoryAddress::new(1, 5);
224+
// Pre-load memory with an Address where an integer condition is expected.
225+
let mut vm = setup_vm(
226+
pc,
227+
fp,
228+
&[(
229+
(fp + 1).unwrap(),
230+
MemoryValue::Address(MemoryAddress::new(8, 8)),
231+
)],
232+
);
233+
// Define a JNZ instruction where the condition points to the address.
234+
let instruction = Instruction::JumpIfNotZero::<F> {
235+
condition: MemOrConstant::MemoryAfterFp { shift: 1 },
236+
dest: MemOrConstant::Constant(F::ONE),
237+
updated_fp: MemOrFp::Fp,
238+
};
239+
// Execute: Attempt to update the PC.
240+
let result = vm.update_pc(&instruction);
241+
// Verify: The operation should fail because a condition must be an integer, not an address.
242+
assert!(matches!(
243+
result,
244+
Err(VirtualMachineError::Memory(MemoryError::ExpectedInteger))
245+
));
246+
}
247+
248+
#[test]
249+
fn test_update_fp_jnz_regular_update() {
250+
// Setup: Initialize the FP to a known address.
251+
let fp = MemoryAddress::new(1, 5);
252+
let mut vm = setup_vm(MemoryAddress::new(0, 0), fp, &[]);
253+
// Define a JNZ instruction that specifies the FP should not change (`MemOrFp::Fp`).
254+
let instruction = Instruction::JumpIfNotZero::<F> {
255+
condition: MemOrConstant::Constant(F::ONE),
256+
dest: MemOrConstant::Constant(F::ONE),
257+
updated_fp: MemOrFp::Fp,
258+
};
259+
// Execute: Update the FP.
260+
vm.update_fp(&instruction).unwrap();
261+
// Verify: The FP should remain unchanged.
262+
assert_eq!(vm.run_context.fp, fp);
263+
}
264+
265+
#[test]
266+
fn test_update_fp_jnz_dst_update() {
267+
// Setup: Initialize the FP and define a new address for it to be updated to.
268+
let fp = MemoryAddress::new(1, 5);
269+
let new_fp = MemoryAddress::new(2, 0);
270+
// Pre-load memory with the new FP address at `fp + 3`.
271+
let mut vm = setup_vm(
272+
MemoryAddress::new(0, 0),
273+
fp,
274+
&[((fp + 3).unwrap(), MemoryValue::Address(new_fp))],
275+
);
276+
// Define a JNZ instruction where `updated_fp` points to the new address in memory.
277+
let instruction = Instruction::JumpIfNotZero::<F> {
278+
condition: MemOrConstant::Constant(F::ONE),
279+
dest: MemOrConstant::Constant(F::ONE),
280+
updated_fp: MemOrFp::MemoryAfterFp { shift: 3 },
281+
};
282+
// Execute: Update the FP.
283+
vm.update_fp(&instruction).unwrap();
284+
// Verify: The FP should be updated to the new address.
285+
assert_eq!(vm.run_context.fp, new_fp);
286+
}
287+
288+
#[test]
289+
fn test_update_fp_jnz_dst_is_int_fails() {
290+
// Setup: Initialize the FP.
291+
let fp = MemoryAddress::new(1, 5);
292+
// Pre-load memory with an integer value where a new FP address is expected.
293+
let mut vm = setup_vm(
294+
MemoryAddress::new(0, 0),
295+
fp,
296+
&[((fp + 3).unwrap(), MemoryValue::Int(F::from_u64(99)))],
297+
);
298+
// Define a JNZ instruction where `updated_fp` points to this integer value.
299+
let instruction = Instruction::JumpIfNotZero::<F> {
300+
condition: MemOrConstant::Constant(F::ONE),
301+
dest: MemOrConstant::Constant(F::ONE),
302+
updated_fp: MemOrFp::MemoryAfterFp { shift: 3 },
303+
};
304+
// Execute: Attempt to update the FP.
305+
let result = vm.update_fp(&instruction);
306+
// Verify: The operation should fail because the new FP value must be an address, not an integer.
307+
assert!(matches!(
308+
result,
309+
Err(VirtualMachineError::Memory(
310+
MemoryError::ExpectedMemoryAddress
311+
))
312+
));
313+
}
314+
}

crates/leanVm/src/memory/address.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ pub struct MemoryAddress {
1313
pub offset: usize,
1414
}
1515

16+
impl MemoryAddress {
17+
pub const fn new(segment_index: usize, offset: usize) -> Self {
18+
Self {
19+
segment_index,
20+
offset,
21+
}
22+
}
23+
}
24+
1625
impl Add<usize> for MemoryAddress {
1726
type Output = Result<Self, MathError>;
1827

0 commit comments

Comments
 (0)