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