zingen/visitor/call.rs
1//! Call Instructions
2//!
3//! This module provides functionality for calling functions, both internal and imported,
4//! within the execution environment. It handles the setup of the call stack, manages
5//! parameters, and ensures that the program counter is correctly adjusted for function
6//! calls.
7
8use crate::{
9 wasm::{HostFunc, ToLSBytes},
10 Error, Function, Result,
11};
12use anyhow::anyhow;
13use opcodes::Cancun as OpCode;
14
15impl Function {
16 /// The call indirect instruction calls a function indirectly
17 /// through an operand indexing into a table.
18 pub fn _call_indirect(
19 &mut self,
20 _type_index: u32,
21 _table_index: u32,
22 _table_byte: u8,
23 ) -> Result<()> {
24 todo!()
25 }
26
27 /// Calls a function specified by its index.
28 ///
29 /// This function determines whether the function is an external import or an internal
30 /// function. If it is an external function, it will call the `call_imported` method.
31 /// Otherwise, it will call the `call_internal` method to handle the internal function call.
32 ///
33 /// # Panics
34 ///
35 /// If an attempt is made to call an external function internally, this function will panic.
36 pub fn _call(&mut self, index: u32) -> Result<()> {
37 if self.env.is_external(index) {
38 panic!("External functions could not be called internally");
39 }
40
41 if self.env.imports.len() as u32 > index {
42 self.call_imported(index)
43 } else {
44 self.call_internal(index)
45 }
46 }
47
48 /// Calls an internal function specified by its index.
49 ///
50 /// This function handles the mechanics of calling an internal function, including:
51 /// - Checking for recursion and returning an error if detected.
52 /// - Recording the current program counter (PC) to manage the return address.
53 /// - Adjusting the stack to accommodate parameters and the return address.
54 /// - Storing parameters in memory and registering the call index in the jump table.
55 ///
56 /// # Errors
57 ///
58 /// Returns an error if recursion is detected or if the function index is invalid.
59 fn call_internal(&mut self, index: u32) -> Result<()> {
60 if self.env.index == Some(index) {
61 return Err(anyhow!(
62 "Recursion is no longer supported in this version. See https://github.com/zink-lang/zink/issues/248"
63 ).into());
64 }
65
66 tracing::debug!("Calling internal function: index={index}");
67 let reserved = self.env.slots.get(&index).unwrap_or(&0);
68 let (params, results) = self.env.funcs.get(&index).unwrap_or(&(0, 0));
69
70 // TODO This is a temporary fix to avoid stack underflow.
71 // We need to find a more elegant solution for this.
72 self.masm.increment_sp(1)?;
73
74 // Store parameters in memory and register the call index in the jump table.
75 for i in (0..*params).rev() {
76 tracing::trace!("Storing local at {} for function {index}", i + reserved);
77 self.masm.push(&((i + reserved) * 0x20).to_ls_bytes())?;
78 self.masm._mstore()?;
79 }
80
81 // Register the label to jump back.
82 let return_pc = self.masm.pc() + 2;
83 self.table.label(self.masm.pc(), return_pc);
84 self.masm._jumpdest()?; // TODO: support same pc different label
85
86 // Register the call index in the jump table.
87 self.table.call(self.masm.pc(), index); // [PUSHN, CALL_PC]
88 self.masm._jump()?;
89
90 // Adjust the stack pointer for the results.
91 self.masm._jumpdest()?;
92 self.masm.increment_sp(*results as u16)?;
93 Ok(())
94 }
95
96 /// Calls an imported function specified by its index.
97 ///
98 /// This function retrieves the imported function from the environment and executes it.
99 /// It handles various host functions and ensures that the correct operations are performed
100 /// based on the function type.
101 ///
102 /// # Errors
103 ///
104 /// Returns an error if the imported function is not found or if an unsupported host function
105 /// is encountered.
106 fn call_imported(&mut self, index: u32) -> Result<()> {
107 // Retrieve the imported function index from the environment.
108 let func = *self
109 .env
110 .imports
111 .get(&index)
112 .ok_or(Error::ImportedFuncNotFound(index))?;
113
114 tracing::trace!("Calling imported function, index={index}, func={func:?}");
115 match func {
116 HostFunc::Evm(OpCode::LOG0) => self.log(0),
117 HostFunc::Evm(OpCode::LOG1) => self.log(1),
118 HostFunc::Evm(OpCode::LOG2) => self.log(2),
119 HostFunc::Evm(OpCode::LOG3) => self.log(3),
120 HostFunc::Evm(OpCode::LOG4) => self.log(4),
121 HostFunc::Evm(op) => self.masm.emit_op(op),
122 HostFunc::U256MAX => self.masm.push(&[255; 32]),
123 HostFunc::Revert(count) => self.revert(count),
124 HostFunc::NoOp | HostFunc::Label(_) => Ok(()),
125 _ => {
126 tracing::error!("Unsupported host function {func:?}");
127 Err(Error::UnsupportedHostFunc(func))
128 }
129 }
130 }
131}