//! Data structures for control flow emission.
use crate::{Error, Result};
use smallvec::SmallVec;
use wasmparser::BlockType;
/// The type of the control stack frame.
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ControlStackFrameType {
/// The if control stack frame.
///
/// true is has else block, otherwise false.
If(bool),
/// The else control stack frame.
Else,
/// The loop control stack frame.
Loop,
/// The block control stack frame.
Block,
}
/// Holds the necessary metadata to support the smission
/// of control flow instructions.
///
/// NOTE: The output of control flow should be placed on
/// the stack, so we don't need to store the result type.
#[derive(Clone)]
pub struct ControlStackFrame {
/// The type of the control stack frame.
///
/// If loop, break it while popping.
pub ty: ControlStackFrameType,
/// The program counter offset at the beginning of if.
pub original_pc_offset: u16,
/// The return values of the block.
///
/// Could be useful for validation.
result: BlockType,
/// Original stack pointer.
pub original_sp: u16,
/// Flag to mark frames that might contain early returns
pub might_return_early: bool,
}
impl ControlStackFrame {
/// Create a new control stack frame.
pub fn new(
ty: ControlStackFrameType,
original_pc_offset: u16,
original_sp: u16,
result: BlockType,
) -> Self {
Self {
ty,
original_pc_offset,
original_sp,
result,
might_return_early: false,
}
}
/// Get the offset of the original program counter.
pub fn pc_offset(&self) -> u16 {
self.original_pc_offset
}
/// Get the result type of the control stack frame.
pub fn result(&self) -> BlockType {
self.result
}
/// Set the flag indicating this frame might contain early returns
pub fn set_might_return_early(&mut self, value: bool) {
self.might_return_early = value;
}
/// Check if this frame is at a function boundary
pub fn is_function_boundary(&self) -> bool {
// For now, we consider Block frames as potential function boundaries
matches!(self.ty, ControlStackFrameType::Block)
}
}
/// The control stack.
#[derive(Default)]
pub struct ControlStack {
/// Stack frames for control flow.
///
/// The 32 is set arbitrarily, we can adjust it as we see fit.
pub stack: SmallVec<[ControlStackFrame; 32]>,
}
impl ControlStack {
/// The total depth of the control stack.
pub fn depth(&self) -> usize {
self.stack.len()
}
/// Mark the else block of an if.
pub fn mark_else(&mut self) -> Result<ControlStackFrame> {
let last = self
.stack
.last_mut()
.ok_or_else(|| Error::ControlStackUnderflow)?;
if last.ty != ControlStackFrameType::If(false) {
return Err(Error::InvalidElseBlock(last.original_pc_offset));
}
last.ty = ControlStackFrameType::If(true);
Ok(last.clone())
}
/// Push a control stack frame.
pub fn push(&mut self, frame: ControlStackFrame) {
self.stack.push(frame);
}
/// Pop a control stack frame.
pub fn pop(&mut self) -> Result<ControlStackFrame> {
self.stack.pop().ok_or_else(|| Error::ControlStackUnderflow)
}
/// Get the label of the control stack frame at given depth.
pub fn label_from_depth(&self, mut depth: u32) -> Result<u16> {
for frame in self.stack.iter().rev() {
if frame.ty == ControlStackFrameType::Else {
continue;
}
if depth == 0 {
return Ok(frame.pc_offset());
}
depth -= 1;
}
Err(Error::InvalidDepth(depth as usize))
}
/// Get a reference to the frame at the given depth
pub fn frame_from_depth(&self, mut depth: u32) -> Result<&ControlStackFrame> {
for (i, frame) in self.stack.iter().rev().enumerate() {
if frame.ty == ControlStackFrameType::Else {
continue;
}
if depth == 0 {
return Ok(&self.stack[self.stack.len() - 1 - i]);
}
depth -= 1;
}
Err(Error::InvalidDepth(depth as usize))
}
/// Check if a branch at the given depth would exit the function
pub fn is_exit_branch(&self, depth: u32) -> bool {
// If depth exceeds our stack, it's definitely an exit
if depth as usize >= self.depth() {
return true;
}
// Get the frame that would be the target
if let Ok(frame) = self.frame_from_depth(depth) {
// If it's a Block at the outermost level (index 0), it might be a function boundary
if matches!(frame.ty, ControlStackFrameType::Block) {
for (i, f) in self.stack.iter().enumerate() {
if f.original_pc_offset == frame.original_pc_offset {
return i == 0; // It's an exit if it's the outermost block
}
}
}
}
false
}
/// Mark frames as potentially having early returns
pub fn mark_frames_with_early_return(&mut self, depth: u32) {
let target_idx = if depth as usize >= self.depth() {
// If targeting beyond the stack, mark all frames
0 // Start from the first frame
} else {
// Find the target frame index
let mut target_idx = self.depth();
let mut current_depth = 0;
for (i, frame) in self.stack.iter().rev().enumerate() {
if frame.ty == ControlStackFrameType::Else {
continue;
}
if current_depth == depth {
target_idx = self.depth() - 1 - i;
break;
}
current_depth += 1;
}
target_idx
};
// Mark frames from target to the end
for i in target_idx..self.depth() {
self.stack[i].set_might_return_early(true);
}
}
/// Get the return type of the control stack frame at given depth.
pub fn ret_ty(&self, depth: usize) -> Result<BlockType> {
if depth == 0 {
return Err(Error::InvalidDepth(depth));
}
self.stack
.get(self.depth() - depth)
.map(|f| f.result)
.ok_or_else(|| Error::InvalidDepth(depth))
}
/// Get the type of the control stack frame at given depth.
pub fn ty(&self, depth: usize) -> Result<ControlStackFrameType> {
if depth == 0 {
return Err(Error::InvalidDepth(depth));
}
self.stack
.get(self.depth() - depth)
.map(|f| f.ty)
.ok_or_else(|| Error::InvalidDepth(depth))
}
/// Get the length of the control stack
pub fn len(&self) -> usize {
self.stack.len()
}
/// Check if the control stack is empty
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
}