cairo_vm/vm/context/
run_context.rs

1use crate::{
2    types::{
3        instruction::{Instruction, Op1Addr, Register},
4        relocatable::{MaybeRelocatable, Relocatable},
5    },
6    vm::errors::{
7        memory_errors::MemoryError::AddressNotRelocatable, vm_errors::VirtualMachineError,
8    },
9};
10use num_traits::abs;
11
12pub struct RunContext {
13    pub(crate) pc: Relocatable,
14    pub(crate) ap: usize,
15    pub(crate) fp: usize,
16}
17
18impl RunContext {
19    pub fn get_ap(&self) -> Relocatable {
20        Relocatable::from((1, self.ap))
21    }
22    pub fn get_fp(&self) -> Relocatable {
23        Relocatable::from((1, self.fp))
24    }
25    pub fn get_pc(&self) -> Relocatable {
26        self.pc
27    }
28
29    pub fn new(pc: Relocatable, ap: usize, fp: usize) -> Self {
30        RunContext { pc, ap, fp }
31    }
32
33    pub fn compute_dst_addr(
34        &self,
35        instruction: &Instruction,
36    ) -> Result<Relocatable, VirtualMachineError> {
37        let base_addr = match instruction.dst_register {
38            Register::AP => self.get_ap(),
39            Register::FP => self.get_fp(),
40        };
41        if instruction.off0 < 0 {
42            Ok((base_addr - abs(instruction.off0) as usize)?)
43        } else {
44            Ok((base_addr + (instruction.off0 as usize))?)
45        }
46    }
47
48    pub fn compute_op0_addr(
49        &self,
50        instruction: &Instruction,
51    ) -> Result<Relocatable, VirtualMachineError> {
52        let base_addr = match instruction.op0_register {
53            Register::AP => self.get_ap(),
54            Register::FP => self.get_fp(),
55        };
56        if instruction.off1 < 0 {
57            Ok((base_addr - abs(instruction.off1) as usize)?)
58        } else {
59            Ok((base_addr + (instruction.off1 as usize))?)
60        }
61    }
62
63    pub fn compute_op1_addr(
64        &self,
65        instruction: &Instruction,
66        op0: Option<&MaybeRelocatable>,
67    ) -> Result<Relocatable, VirtualMachineError> {
68        let base_addr = match instruction.op1_addr {
69            Op1Addr::FP => self.get_fp(),
70            Op1Addr::AP => self.get_ap(),
71            Op1Addr::Imm => match instruction.off2 == 1 {
72                true => self.pc,
73                false => return Err(VirtualMachineError::ImmShouldBe1),
74            },
75            Op1Addr::Op0 => match op0 {
76                Some(MaybeRelocatable::RelocatableValue(addr)) => *addr,
77                Some(_) => return Err(VirtualMachineError::Memory(AddressNotRelocatable)),
78                None => return Err(VirtualMachineError::UnknownOp0),
79            },
80        };
81        if instruction.off2 < 0 {
82            Ok((base_addr - abs(instruction.off2) as usize)?)
83        } else {
84            Ok((base_addr + (instruction.off2 as usize))?)
85        }
86    }
87
88    #[doc(hidden)]
89    pub(crate) fn set_ap(&mut self, ap: usize) {
90        self.ap = ap;
91    }
92
93    #[doc(hidden)]
94    pub(crate) fn set_fp(&mut self, fp: usize) {
95        self.fp = fp;
96    }
97
98    #[doc(hidden)]
99    pub(crate) fn set_pc(&mut self, pc: Relocatable) {
100        self.pc = pc;
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::relocatable;
108    use crate::stdlib::string::ToString;
109    use crate::types::instruction::{ApUpdate, FpUpdate, Opcode, OpcodeExtension, PcUpdate, Res};
110    use crate::utils::test_utils::mayberelocatable;
111    use crate::vm::errors::memory_errors::MemoryError;
112    use crate::Felt252;
113    use assert_matches::assert_matches;
114
115    #[cfg(target_arch = "wasm32")]
116    use wasm_bindgen_test::*;
117
118    #[test]
119    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
120    fn compute_dst_addr_for_ap_register() {
121        let instruction = Instruction {
122            off0: 1,
123            off1: 2,
124            off2: 3,
125            dst_register: Register::AP,
126            op0_register: Register::FP,
127            op1_addr: Op1Addr::AP,
128            res: Res::Add,
129            pc_update: PcUpdate::Regular,
130            ap_update: ApUpdate::Regular,
131            fp_update: FpUpdate::Regular,
132            opcode: Opcode::NOp,
133            opcode_extension: OpcodeExtension::Stone,
134        };
135
136        let run_context = RunContext {
137            pc: relocatable!(0, 4),
138            ap: 5,
139            fp: 6,
140        };
141        assert_matches!(
142            run_context.compute_dst_addr(&instruction),
143            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(1, 6)
144        );
145    }
146
147    #[test]
148    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
149    fn compute_dst_addr_for_fp_register() {
150        let instruction = Instruction {
151            off0: 1,
152            off1: 2,
153            off2: 3,
154            dst_register: Register::FP,
155            op0_register: Register::AP,
156            op1_addr: Op1Addr::AP,
157            res: Res::Add,
158            pc_update: PcUpdate::Regular,
159            ap_update: ApUpdate::Regular,
160            fp_update: FpUpdate::Regular,
161            opcode: Opcode::NOp,
162            opcode_extension: OpcodeExtension::Stone,
163        };
164
165        let run_context = RunContext {
166            pc: relocatable!(0, 4),
167            ap: 5,
168            fp: 6,
169        };
170
171        assert_matches!(
172            run_context.compute_dst_addr(&instruction),
173            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(1, 7)
174        );
175    }
176
177    #[test]
178    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
179    fn compute_op0_addr_for_ap_register() {
180        let instruction = Instruction {
181            off0: 1,
182            off1: 2,
183            off2: 3,
184            dst_register: Register::AP,
185            op0_register: Register::AP,
186            op1_addr: Op1Addr::AP,
187            res: Res::Add,
188            pc_update: PcUpdate::Regular,
189            ap_update: ApUpdate::Regular,
190            fp_update: FpUpdate::Regular,
191            opcode: Opcode::NOp,
192            opcode_extension: OpcodeExtension::Stone,
193        };
194
195        let run_context = RunContext {
196            pc: relocatable!(0, 4),
197            ap: 5,
198            fp: 6,
199        };
200        assert_matches!(
201            run_context.compute_op0_addr(&instruction),
202            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(1, 7)
203        );
204    }
205
206    #[test]
207    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
208    fn compute_op0_addr_for_fp_register() {
209        let instruction = Instruction {
210            off0: 1,
211            off1: 2,
212            off2: 3,
213            dst_register: Register::FP,
214            op0_register: Register::FP,
215            op1_addr: Op1Addr::AP,
216            res: Res::Add,
217            pc_update: PcUpdate::Regular,
218            ap_update: ApUpdate::Regular,
219            fp_update: FpUpdate::Regular,
220            opcode: Opcode::NOp,
221            opcode_extension: OpcodeExtension::Stone,
222        };
223
224        let run_context = RunContext {
225            pc: relocatable!(0, 4),
226            ap: 5,
227            fp: 6,
228        };
229        assert_matches!(
230            run_context.compute_op0_addr(&instruction),
231            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(1, 8)
232        );
233    }
234
235    #[test]
236    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
237    fn compute_op1_addr_for_fp_op1_addr() {
238        let instruction = Instruction {
239            off0: 1,
240            off1: 2,
241            off2: 3,
242            dst_register: Register::FP,
243            op0_register: Register::AP,
244            op1_addr: Op1Addr::FP,
245            res: Res::Add,
246            pc_update: PcUpdate::Regular,
247            ap_update: ApUpdate::Regular,
248            fp_update: FpUpdate::Regular,
249            opcode: Opcode::NOp,
250            opcode_extension: OpcodeExtension::Stone,
251        };
252
253        let run_context = RunContext {
254            pc: relocatable!(0, 4),
255            ap: 5,
256            fp: 6,
257        };
258        assert_matches!(
259            run_context.compute_op1_addr(&instruction, None),
260            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(1, 9)
261        );
262    }
263
264    #[test]
265    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
266    fn compute_op1_addr_for_ap_op1_addr() {
267        let instruction = Instruction {
268            off0: 1,
269            off1: 2,
270            off2: 3,
271            dst_register: Register::FP,
272            op0_register: Register::AP,
273            op1_addr: Op1Addr::AP,
274            res: Res::Add,
275            pc_update: PcUpdate::Regular,
276            ap_update: ApUpdate::Regular,
277            fp_update: FpUpdate::Regular,
278            opcode: Opcode::NOp,
279            opcode_extension: OpcodeExtension::Stone,
280        };
281
282        let run_context = RunContext {
283            pc: relocatable!(0, 4),
284            ap: 5,
285            fp: 6,
286        };
287        assert_matches!(
288            run_context.compute_op1_addr(&instruction, None),
289            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(1, 8)
290        );
291    }
292
293    #[test]
294    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
295    fn compute_op1_addr_for_imm_op1_addr_correct_off2() {
296        let instruction = Instruction {
297            off0: 1,
298            off1: 2,
299            off2: 1,
300            dst_register: Register::FP,
301            op0_register: Register::AP,
302            op1_addr: Op1Addr::Imm,
303            res: Res::Add,
304            pc_update: PcUpdate::Regular,
305            ap_update: ApUpdate::Regular,
306            fp_update: FpUpdate::Regular,
307            opcode: Opcode::NOp,
308            opcode_extension: OpcodeExtension::Stone,
309        };
310
311        let run_context = RunContext {
312            pc: relocatable!(0, 4),
313            ap: 5,
314            fp: 6,
315        };
316        assert_matches!(
317            run_context.compute_op1_addr(&instruction, None),
318            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(0, 5)
319        );
320    }
321
322    #[test]
323    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
324    fn compute_op1_addr_for_imm_op1_addr_incorrect_off2() {
325        let instruction = Instruction {
326            off0: 1,
327            off1: 2,
328            off2: 3,
329            dst_register: Register::FP,
330            op0_register: Register::AP,
331            op1_addr: Op1Addr::Imm,
332            res: Res::Add,
333            pc_update: PcUpdate::Regular,
334            ap_update: ApUpdate::Regular,
335            fp_update: FpUpdate::Regular,
336            opcode: Opcode::NOp,
337            opcode_extension: OpcodeExtension::Stone,
338        };
339
340        let run_context = RunContext {
341            pc: relocatable!(0, 4),
342            ap: 5,
343            fp: 6,
344        };
345
346        let error = run_context.compute_op1_addr(&instruction, None);
347        assert_matches!(error, Err(VirtualMachineError::ImmShouldBe1));
348        assert_eq!(
349            error.unwrap_err().to_string(),
350            "In immediate mode, off2 should be 1"
351        );
352    }
353
354    #[test]
355    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
356    fn compute_op1_addr_for_op0_op1_addr_with_op0() {
357        let instruction = Instruction {
358            off0: 1,
359            off1: 2,
360            off2: 1,
361            dst_register: Register::FP,
362            op0_register: Register::AP,
363            op1_addr: Op1Addr::Op0,
364            res: Res::Add,
365            pc_update: PcUpdate::Regular,
366            ap_update: ApUpdate::Regular,
367            fp_update: FpUpdate::Regular,
368            opcode: Opcode::NOp,
369            opcode_extension: OpcodeExtension::Stone,
370        };
371
372        let run_context = RunContext {
373            pc: relocatable!(0, 4),
374            ap: 5,
375            fp: 6,
376        };
377
378        let op0 = mayberelocatable!(1, 7);
379        assert_matches!(
380            run_context.compute_op1_addr(&instruction, Some(&op0)),
381            Ok::<Relocatable, VirtualMachineError>(x) if x == relocatable!(1, 8)
382        );
383    }
384
385    #[test]
386    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
387    fn compute_op1_addr_with_no_relocatable_address() {
388        let instruction = Instruction {
389            off0: 1,
390            off1: 2,
391            off2: 1,
392            dst_register: Register::FP,
393            op0_register: Register::AP,
394            op1_addr: Op1Addr::Op0,
395            res: Res::Add,
396            pc_update: PcUpdate::Regular,
397            ap_update: ApUpdate::Regular,
398            fp_update: FpUpdate::Regular,
399            opcode: Opcode::NOp,
400            opcode_extension: OpcodeExtension::Stone,
401        };
402
403        let run_context = RunContext {
404            pc: relocatable!(0, 4),
405            ap: 5,
406            fp: 6,
407        };
408
409        let op0 = MaybeRelocatable::from(Felt252::from(7));
410        assert_matches!(
411            run_context.compute_op1_addr(&instruction, Some(&op0)),
412            Err::<Relocatable, VirtualMachineError>(VirtualMachineError::Memory(
413                MemoryError::AddressNotRelocatable
414            ))
415        );
416    }
417
418    #[test]
419    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
420    fn compute_op1_addr_for_op0_op1_addr_without_op0() {
421        let instruction = Instruction {
422            off0: 1,
423            off1: 2,
424            off2: 3,
425            dst_register: Register::FP,
426            op0_register: Register::AP,
427            op1_addr: Op1Addr::Op0,
428            res: Res::Add,
429            pc_update: PcUpdate::Regular,
430            ap_update: ApUpdate::Regular,
431            fp_update: FpUpdate::Regular,
432            opcode: Opcode::NOp,
433            opcode_extension: OpcodeExtension::Stone,
434        };
435
436        let run_context = RunContext {
437            pc: relocatable!(0, 4),
438            ap: 5,
439            fp: 6,
440        };
441
442        let error = run_context.compute_op1_addr(&instruction, None);
443        assert_matches!(error, Err(VirtualMachineError::UnknownOp0));
444        assert_eq!(
445            error.unwrap_err().to_string(),
446            "op0 must be known in double dereference"
447        );
448    }
449}