//! Control flow visitors
use crate::{
control::{ControlStackFrame, ControlStackFrameType},
Function, Result,
};
use wasmparser::{BlockType, BrTable};
impl Function {
/// The beginning of an if construct with an implicit block.
pub fn _if(&mut self, blockty: BlockType) -> Result<()> {
// Emit iszero to check the condition.
self.masm._iszero()?;
// push an `If` frame to the control stack
let frame = ControlStackFrame::new(
ControlStackFrameType::If(false),
self.masm.pc(),
self.masm.sp(),
blockty,
);
self.control.push(frame);
// mock the stack output of the counter
//
// the program counter operators should be patched afterwards.
self.masm.asm.increment_sp(1)?;
self.masm._jumpi()?;
Ok(())
}
/// The begeinning of a block construct. A sequence of
/// instructions with a label at the end.
pub fn _block(&mut self, blockty: BlockType) -> Result<()> {
let frame = ControlStackFrame::new(
ControlStackFrameType::Block,
self.masm.pc(),
self.masm.sp(),
blockty,
);
self.masm._jumpdest()?;
self.control.push(frame);
Ok(())
}
/// A block with a label which may be used to
/// form loops.
pub fn _loop(&mut self, blockty: BlockType) -> Result<()> {
let frame = ControlStackFrame::new(
ControlStackFrameType::Loop,
self.masm.pc(),
self.masm.sp(),
blockty,
);
self.masm._jumpdest()?;
self.control.push(frame);
Ok(())
}
/// Marks an else block of an if.
pub fn _else(&mut self) -> Result<()> {
let last_frame = self.control.mark_else()?;
// push an `Else` frame to the control stack.
let frame = ControlStackFrame::new(
ControlStackFrameType::Else,
self.masm.pc(),
self.masm.sp(),
last_frame.result(),
);
self.control.push(frame);
self.masm.asm.increment_sp(1)?;
self.masm._jump()?;
// mark else as the jump destination of the if block.
self.table
.label(last_frame.original_pc_offset, self.masm.pc());
self.masm._jumpdest()?;
Ok(())
}
/// The select instruction selects one of its first two operands based
/// on whether its third operand is zero or not.
///
/// STACK: [cond, val2, val1] -> \[val1\] if cond is non-zero, \[val2\] otherwise.
pub fn _select(&mut self) -> Result<()> {
tracing::trace!("select");
self.masm._iszero()?;
self.masm.increment_sp(1)?;
self.table.label(self.masm.pc(), self.masm.pc() + 2);
self.masm._jumpi()?;
self.masm._drop()?;
self.masm._jumpdest()?;
Ok(())
}
/// Branch to a given label in an enclosing construct.
///
/// Performs an unconditional branch.
pub fn _br(&mut self, depth: u32) -> Result<()> {
// Get the target label
let label = self.control.label_from_depth(depth)?;
// Check if this is a branch that would exit the function
let _is_exit_branch = self.control.is_exit_branch(depth);
// Mark affected frames as having potential early returns
self.control.mark_frames_with_early_return(depth);
// Set up jump target in the jump table
self.table.label(self.masm.pc(), label);
// Emit unconditional jump instruction
self.masm._jump()?;
Ok(())
}
/// Performs a conditional branch if i32 is non-zero.
///
/// Conditional branch to a given label in an enclosing construct.
pub fn _br_if(&mut self, depth: u32) -> Result<()> {
let label = self.control.label_from_depth(depth)?;
// Register the jump target in the jump table
self.table.label(self.masm.pc(), label);
// for a conditional branch, we need to:
//
// increment the stack pointer (for JUMPI's arguments) and
self.masm.increment_sp(1)?;
// emit the conditional jump instruction
self.masm._jumpi()?;
Ok(())
}
/// A jump table which jumps to a label in an enclosing construct.
///
/// Performs an indirect branch through an operand indexing into the
/// label vector that is an immediate to the instruction, or to the
/// default target if the operand is out of bounds.
pub fn _br_table(&mut self, _table: BrTable<'_>) -> Result<()> {
todo!()
}
/// Handle the end of instructions for different situations.
///
/// - End of control flow operators.
/// - End of function.
/// - End of program.
pub fn _end(&mut self) -> Result<()> {
if let Ok(frame) = self.control.pop() {
return self.handle_frame_popping(frame);
}
let results = self.ty.results();
if self.is_main || self.abi.is_some() {
tracing::trace!("end of main function");
self.masm.main_return(results)
} else {
tracing::trace!("end of call");
self.masm.call_return(results)
}
}
/// Mark as invalid for now.
///
/// TODO: recheck this implementation, if it is okay,
/// provide more docs.
pub fn _unreachable(&mut self) -> Result<()> {
self.masm._invalid()?;
Ok(())
}
/// Perform nothing in EVM bytecode.
pub fn _nop(&mut self) -> Result<()> {
Ok(())
}
/// Handle the popping of a frame.
///
/// TODO: validate stack IO for all frames (#59)
pub(crate) fn handle_frame_popping(&mut self, frame: ControlStackFrame) -> Result<()> {
match frame.ty {
ControlStackFrameType::If(true) => Ok(()),
ControlStackFrameType::Block => {
// For blocks that might have early returns, ensure proper jump destination
if frame.might_return_early {
// Make sure the jump table has this position as a target for the block
self.table.label(frame.original_pc_offset, self.masm.pc());
}
self.masm._jumpdest()
}
ControlStackFrameType::Loop => Ok(()),
_ => {
self.table.label(frame.original_pc_offset, self.masm.pc());
// TODO: Check the stack output and make decisions
// how to handle the results.
// Emit JUMPDEST at the end of the control flow.
self.masm._jumpdest()
}
}
}
}