use crate::{Error, Result};
use smallvec::SmallVec;
use wasmparser::BlockType;
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ControlStackFrameType {
If(bool),
Else,
Loop,
Block,
}
#[derive(Clone)]
pub struct ControlStackFrame {
pub ty: ControlStackFrameType,
pub original_pc_offset: u16,
result: BlockType,
pub original_sp: u16,
pub might_return_early: bool,
}
impl ControlStackFrame {
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,
}
}
pub fn pc_offset(&self) -> u16 {
self.original_pc_offset
}
pub fn result(&self) -> BlockType {
self.result
}
pub fn set_might_return_early(&mut self, value: bool) {
self.might_return_early = value;
}
pub fn is_function_boundary(&self) -> bool {
matches!(self.ty, ControlStackFrameType::Block)
}
}
#[derive(Default)]
pub struct ControlStack {
pub stack: SmallVec<[ControlStackFrame; 32]>,
}
impl ControlStack {
pub fn depth(&self) -> usize {
self.stack.len()
}
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())
}
pub fn push(&mut self, frame: ControlStackFrame) {
self.stack.push(frame);
}
pub fn pop(&mut self) -> Result<ControlStackFrame> {
self.stack.pop().ok_or_else(|| Error::ControlStackUnderflow)
}
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))
}
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))
}
pub fn is_exit_branch(&self, depth: u32) -> bool {
if depth as usize >= self.depth() {
return true;
}
if let Ok(frame) = self.frame_from_depth(depth) {
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; }
}
}
}
false
}
pub fn mark_frames_with_early_return(&mut self, depth: u32) {
let target_idx = if depth as usize >= self.depth() {
0 } else {
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
};
for i in target_idx..self.depth() {
self.stack[i].set_might_return_early(true);
}
}
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))
}
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))
}
pub fn len(&self) -> usize {
self.stack.len()
}
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
}