use {
super::{ExecutionResult, Vm},
crate::{errors::ExecutionError, inst_param::Number, instruction::Instruction},
};
pub fn execute_call_immediate(vm: &mut dyn Vm, inst: &Instruction) -> ExecutionResult<()> {
match &inst.imm {
Some(either::Either::Left(syscall_name)) => {
let result = vm.handle_syscall(syscall_name)?;
vm.set_register(0, result);
vm.advance_pc();
Ok(())
}
Some(either::Either::Right(Number::Int(target))) => {
if vm.get_call_depth() >= vm.max_call_depth() {
return Err(ExecutionError::CallDepthExceeded(vm.max_call_depth()));
}
let saved_registers = [
vm.get_register(6),
vm.get_register(7),
vm.get_register(8),
vm.get_register(9),
];
let saved_frame_pointer = vm.get_register(10);
let return_pc = vm.get_pc() + 1;
vm.push_frame(return_pc, saved_registers, saved_frame_pointer)?;
vm.set_register(
10,
saved_frame_pointer.wrapping_add(vm.get_stack_frame_size()),
);
let target_pc = ((vm.get_pc() as i64) + 1 + *target) as usize;
vm.set_pc(target_pc);
Ok(())
}
_ => Err(ExecutionError::InvalidOperand),
}
}
pub fn execute_call_register(vm: &mut dyn Vm, inst: &Instruction) -> ExecutionResult<()> {
let reg_num = match &inst.dst {
Some(reg) => reg.n as usize,
_ => return Err(ExecutionError::InvalidOperand),
};
if reg_num >= 10 {
return Err(ExecutionError::InvalidOperand);
}
let target = vm.get_register(reg_num) as usize;
if vm.get_call_depth() >= vm.max_call_depth() {
return Err(ExecutionError::CallDepthExceeded(vm.max_call_depth()));
}
let saved_registers = [
vm.get_register(6),
vm.get_register(7),
vm.get_register(8),
vm.get_register(9),
];
let saved_frame_pointer = vm.get_register(10);
let return_pc = vm.get_pc() + 1;
vm.push_frame(return_pc, saved_registers, saved_frame_pointer)?;
vm.set_register(
10,
saved_frame_pointer.wrapping_add(vm.get_stack_frame_size()),
);
vm.set_pc(target);
Ok(())
}
pub fn execute_exit(vm: &mut dyn Vm, _inst: &Instruction) -> ExecutionResult<()> {
if let Some((return_pc, saved_registers, saved_frame_pointer)) = vm.pop_frame() {
vm.set_register(6, saved_registers[0]);
vm.set_register(7, saved_registers[1]);
vm.set_register(8, saved_registers[2]);
vm.set_register(9, saved_registers[3]);
vm.set_register(10, saved_frame_pointer);
vm.set_pc(return_pc);
Ok(())
} else {
vm.halt(vm.get_register(0));
Ok(())
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
errors::ExecutionError,
execute::{MockVm, make_test_instruction},
inst_param::{Number, Register},
opcode::Opcode,
},
either::Either,
};
#[test]
fn test_syscall() {
let inst = make_test_instruction(
Opcode::Call,
None,
None,
None,
Some(Either::Left("sol_log_".to_string())),
);
let mut vm = MockVm::new();
execute_call_immediate(&mut vm, &inst).unwrap();
assert_eq!(vm.pc, 1);
assert_eq!(vm.syscall_logs.len(), 1);
assert_eq!(vm.syscall_logs[0], "sol_log_");
}
#[test]
fn test_internal_call() {
let inst = make_test_instruction(
Opcode::Call,
None,
None,
None,
Some(Either::Right(Number::Int(10))),
);
let mut vm = MockVm::new();
vm.registers[6] = 100;
vm.registers[7] = 200;
execute_call_immediate(&mut vm, &inst).unwrap();
assert_eq!(vm.pc, 11); assert_eq!(vm.call_stack.len(), 1);
assert_eq!(vm.call_stack[0].0, 1);
assert_eq!(vm.call_stack[0].1[0], 100); assert_eq!(vm.call_stack[0].1[1], 200); }
#[test]
fn test_internal_call_depth_exceeded() {
let inst = make_test_instruction(
Opcode::Call,
None,
None,
None,
Some(Either::Right(Number::Int(10))),
);
let mut vm = MockVm::new();
vm.call_depth_limit = 1;
execute_call_immediate(&mut vm, &inst).unwrap();
assert_eq!(vm.call_stack.len(), 1);
let result = execute_call_immediate(&mut vm, &inst);
assert!(matches!(result, Err(ExecutionError::CallDepthExceeded(1))));
}
#[test]
fn test_callx() {
let inst = make_test_instruction(Opcode::Callx, Some(Register { n: 5 }), None, None, None);
let mut vm = MockVm::new();
vm.registers[5] = 20; vm.registers[6] = 100;
vm.registers[7] = 200;
execute_call_register(&mut vm, &inst).unwrap();
assert_eq!(vm.pc, 20); assert_eq!(vm.call_stack.len(), 1);
assert_eq!(vm.call_stack[0].0, 1);
assert_eq!(vm.call_stack[0].1[0], 100); assert_eq!(vm.call_stack[0].1[1], 200); }
#[test]
fn test_callx_r10_invalid() {
let inst = make_test_instruction(Opcode::Callx, Some(Register { n: 10 }), None, None, None);
let mut vm = MockVm::new();
let result = execute_call_register(&mut vm, &inst);
assert!(matches!(result, Err(ExecutionError::InvalidOperand)));
}
#[test]
fn test_callx_depth_exceeded() {
let inst = make_test_instruction(Opcode::Callx, Some(Register { n: 5 }), None, None, None);
let mut vm = MockVm::new();
vm.registers[5] = 0;
vm.call_depth_limit = 1;
execute_call_register(&mut vm, &inst).unwrap();
assert_eq!(vm.call_stack.len(), 1);
let result = execute_call_register(&mut vm, &inst);
assert!(matches!(result, Err(ExecutionError::CallDepthExceeded(1))));
}
#[test]
fn test_exit_halt() {
let inst = make_test_instruction(Opcode::Exit, None, None, None, None);
let mut vm = MockVm::new();
vm.registers[0] = 42;
execute_exit(&mut vm, &inst).unwrap();
assert!(vm.halted);
assert_eq!(vm.exit_code, Some(42));
}
#[test]
fn test_exit_return() {
let inst = make_test_instruction(Opcode::Exit, None, None, None, None);
let mut vm = MockVm::new();
vm.call_stack.push((5, [100, 200, 300, 400], 0x1000));
vm.registers[0] = 99;
execute_exit(&mut vm, &inst).unwrap();
assert!(!vm.halted);
assert_eq!(vm.pc, 5); assert_eq!(vm.registers[6], 100);
assert_eq!(vm.registers[7], 200);
assert_eq!(vm.registers[8], 300);
assert_eq!(vm.registers[9], 400);
assert_eq!(vm.registers[10], 0x1000);
}
}