Skip to main content

sbpf_common/execute/
call.rs

1use {
2    super::{ExecutionResult, Vm},
3    crate::{errors::ExecutionError, inst_param::Number, instruction::Instruction},
4};
5
6pub fn execute_call_immediate(vm: &mut dyn Vm, inst: &Instruction) -> ExecutionResult<()> {
7    match &inst.imm {
8        Some(either::Either::Left(syscall_name)) => {
9            let result = vm.handle_syscall(syscall_name)?;
10            vm.set_register(0, result);
11            vm.advance_pc();
12            Ok(())
13        }
14        Some(either::Either::Right(Number::Int(target))) => {
15            if vm.get_call_depth() >= vm.max_call_depth() {
16                return Err(ExecutionError::CallDepthExceeded(vm.max_call_depth()));
17            }
18            let saved_registers = [
19                vm.get_register(6),
20                vm.get_register(7),
21                vm.get_register(8),
22                vm.get_register(9),
23            ];
24            let saved_frame_pointer = vm.get_register(10);
25            let return_pc = vm.get_pc() + 1;
26            vm.push_frame(return_pc, saved_registers, saved_frame_pointer)?;
27            vm.set_register(
28                10,
29                saved_frame_pointer.wrapping_add(vm.get_stack_frame_size()),
30            );
31            let target_pc = ((vm.get_pc() as i64) + 1 + *target) as usize;
32            vm.set_pc(target_pc);
33            Ok(())
34        }
35        _ => Err(ExecutionError::InvalidOperand),
36    }
37}
38
39pub fn execute_call_register(vm: &mut dyn Vm, inst: &Instruction) -> ExecutionResult<()> {
40    let reg_num = match &inst.dst {
41        Some(reg) => reg.n as usize,
42        _ => return Err(ExecutionError::InvalidOperand),
43    };
44    if reg_num >= 10 {
45        return Err(ExecutionError::InvalidOperand);
46    }
47    let target = vm.get_register(reg_num) as usize;
48
49    if vm.get_call_depth() >= vm.max_call_depth() {
50        return Err(ExecutionError::CallDepthExceeded(vm.max_call_depth()));
51    }
52
53    let saved_registers = [
54        vm.get_register(6),
55        vm.get_register(7),
56        vm.get_register(8),
57        vm.get_register(9),
58    ];
59    let saved_frame_pointer = vm.get_register(10);
60    let return_pc = vm.get_pc() + 1;
61    vm.push_frame(return_pc, saved_registers, saved_frame_pointer)?;
62    vm.set_register(
63        10,
64        saved_frame_pointer.wrapping_add(vm.get_stack_frame_size()),
65    );
66    vm.set_pc(target);
67    Ok(())
68}
69
70pub fn execute_exit(vm: &mut dyn Vm, _inst: &Instruction) -> ExecutionResult<()> {
71    if let Some((return_pc, saved_registers, saved_frame_pointer)) = vm.pop_frame() {
72        vm.set_register(6, saved_registers[0]);
73        vm.set_register(7, saved_registers[1]);
74        vm.set_register(8, saved_registers[2]);
75        vm.set_register(9, saved_registers[3]);
76        vm.set_register(10, saved_frame_pointer);
77        vm.set_pc(return_pc);
78        Ok(())
79    } else {
80        vm.halt(vm.get_register(0));
81        Ok(())
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use {
88        super::*,
89        crate::{
90            errors::ExecutionError,
91            execute::{MockVm, make_test_instruction},
92            inst_param::{Number, Register},
93            opcode::Opcode,
94        },
95        either::Either,
96    };
97
98    #[test]
99    fn test_syscall() {
100        // call sol_log_
101        let inst = make_test_instruction(
102            Opcode::Call,
103            None,
104            None,
105            None,
106            Some(Either::Left("sol_log_".to_string())),
107        );
108        let mut vm = MockVm::new();
109
110        execute_call_immediate(&mut vm, &inst).unwrap();
111
112        assert_eq!(vm.pc, 1);
113        assert_eq!(vm.syscall_logs.len(), 1);
114        assert_eq!(vm.syscall_logs[0], "sol_log_");
115    }
116
117    #[test]
118    fn test_internal_call() {
119        // call 10
120        let inst = make_test_instruction(
121            Opcode::Call,
122            None,
123            None,
124            None,
125            Some(Either::Right(Number::Int(10))),
126        );
127        let mut vm = MockVm::new();
128        vm.registers[6] = 100;
129        vm.registers[7] = 200;
130
131        execute_call_immediate(&mut vm, &inst).unwrap();
132
133        assert_eq!(vm.pc, 11); // jumped to target
134        assert_eq!(vm.call_stack.len(), 1);
135        assert_eq!(vm.call_stack[0].0, 1);
136        assert_eq!(vm.call_stack[0].1[0], 100); // r6
137        assert_eq!(vm.call_stack[0].1[1], 200); // r7
138    }
139
140    #[test]
141    fn test_internal_call_depth_exceeded() {
142        // call 10
143        let inst = make_test_instruction(
144            Opcode::Call,
145            None,
146            None,
147            None,
148            Some(Either::Right(Number::Int(10))),
149        );
150
151        let mut vm = MockVm::new();
152        vm.call_depth_limit = 1;
153
154        // first call succeeds
155        execute_call_immediate(&mut vm, &inst).unwrap();
156        assert_eq!(vm.call_stack.len(), 1);
157
158        // second call fails
159        let result = execute_call_immediate(&mut vm, &inst);
160        assert!(matches!(result, Err(ExecutionError::CallDepthExceeded(1))));
161    }
162
163    #[test]
164    fn test_callx() {
165        // callx r5
166        let inst = make_test_instruction(Opcode::Callx, Some(Register { n: 5 }), None, None, None);
167        let mut vm = MockVm::new();
168        vm.registers[5] = 20; // target address
169        vm.registers[6] = 100;
170        vm.registers[7] = 200;
171
172        execute_call_register(&mut vm, &inst).unwrap();
173
174        assert_eq!(vm.pc, 20); // jumped to target
175        assert_eq!(vm.call_stack.len(), 1);
176        assert_eq!(vm.call_stack[0].0, 1);
177        assert_eq!(vm.call_stack[0].1[0], 100); // r6
178        assert_eq!(vm.call_stack[0].1[1], 200); // r7
179    }
180
181    #[test]
182    fn test_callx_r10_invalid() {
183        // callx r10
184        let inst = make_test_instruction(Opcode::Callx, Some(Register { n: 10 }), None, None, None);
185        let mut vm = MockVm::new();
186
187        let result = execute_call_register(&mut vm, &inst);
188        assert!(matches!(result, Err(ExecutionError::InvalidOperand)));
189    }
190
191    #[test]
192    fn test_callx_depth_exceeded() {
193        // callx r5
194        let inst = make_test_instruction(Opcode::Callx, Some(Register { n: 5 }), None, None, None);
195        let mut vm = MockVm::new();
196        vm.registers[5] = 0;
197        vm.call_depth_limit = 1;
198
199        // first call succeeds
200        execute_call_register(&mut vm, &inst).unwrap();
201        assert_eq!(vm.call_stack.len(), 1);
202
203        // second call fails
204        let result = execute_call_register(&mut vm, &inst);
205        assert!(matches!(result, Err(ExecutionError::CallDepthExceeded(1))));
206    }
207
208    #[test]
209    fn test_exit_halt() {
210        // exit - halts with r0 as exit code
211        let inst = make_test_instruction(Opcode::Exit, None, None, None, None);
212        let mut vm = MockVm::new();
213        vm.registers[0] = 42; // exit code
214
215        execute_exit(&mut vm, &inst).unwrap();
216
217        assert!(vm.halted);
218        assert_eq!(vm.exit_code, Some(42));
219    }
220
221    #[test]
222    fn test_exit_return() {
223        // exit from a function call - should return to caller
224        let inst = make_test_instruction(Opcode::Exit, None, None, None, None);
225        let mut vm = MockVm::new();
226
227        // Simulate being in a function call
228        vm.call_stack.push((5, [100, 200, 300, 400], 0x1000));
229        vm.registers[0] = 99;
230
231        execute_exit(&mut vm, &inst).unwrap();
232
233        assert!(!vm.halted);
234        assert_eq!(vm.pc, 5); // returned to saved PC
235        assert_eq!(vm.registers[6], 100);
236        assert_eq!(vm.registers[7], 200);
237        assert_eq!(vm.registers[8], 300);
238        assert_eq!(vm.registers[9], 400);
239        assert_eq!(vm.registers[10], 0x1000);
240    }
241}