zingen/
control.rs

1//! Data structures for control flow emission.
2use crate::{Error, Result};
3use smallvec::SmallVec;
4use wasmparser::BlockType;
5
6/// The type of the control stack frame.
7#[repr(u8)]
8#[derive(Clone, Copy, PartialEq, Eq, Debug)]
9pub enum ControlStackFrameType {
10    /// The if control stack frame.
11    ///
12    /// true is has else block, otherwise false.
13    If(bool),
14    /// The else control stack frame.
15    Else,
16    /// The loop control stack frame.
17    Loop,
18    /// The block control stack frame.
19    Block,
20}
21
22/// Holds the necessary metadata to support the smission
23/// of control flow instructions.
24///
25/// NOTE: The output of control flow should be placed on
26/// the stack, so we don't need to store the result type.
27#[derive(Clone)]
28pub struct ControlStackFrame {
29    /// The type of the control stack frame.
30    ///
31    /// If loop, break it while popping.
32    pub ty: ControlStackFrameType,
33    /// The program counter offset at the beginning of if.
34    pub original_pc_offset: u16,
35    /// The return values of the block.
36    ///
37    /// Could be useful for validation.
38    result: BlockType,
39
40    /// Original stack pointer.
41    pub original_sp: u16,
42
43    /// Flag to mark frames that might contain early returns
44    pub might_return_early: bool,
45}
46
47impl ControlStackFrame {
48    /// Create a new control stack frame.
49    pub fn new(
50        ty: ControlStackFrameType,
51        original_pc_offset: u16,
52        original_sp: u16,
53        result: BlockType,
54    ) -> Self {
55        Self {
56            ty,
57            original_pc_offset,
58            original_sp,
59            result,
60            might_return_early: false,
61        }
62    }
63
64    /// Get the offset of the original program counter.
65    pub fn pc_offset(&self) -> u16 {
66        self.original_pc_offset
67    }
68
69    /// Get the result type of the control stack frame.
70    pub fn result(&self) -> BlockType {
71        self.result
72    }
73
74    /// Set the flag indicating this frame might contain early returns
75    pub fn set_might_return_early(&mut self, value: bool) {
76        self.might_return_early = value;
77    }
78
79    /// Check if this frame is at a function boundary
80    pub fn is_function_boundary(&self) -> bool {
81        // For now, we consider Block frames as potential function boundaries
82        matches!(self.ty, ControlStackFrameType::Block)
83    }
84}
85
86/// The control stack.
87#[derive(Default)]
88pub struct ControlStack {
89    /// Stack frames for control flow.
90    ///
91    /// The 32 is set arbitrarily, we can adjust it as we see fit.
92    pub stack: SmallVec<[ControlStackFrame; 32]>,
93}
94
95impl ControlStack {
96    /// The total depth of the control stack.
97    pub fn depth(&self) -> usize {
98        self.stack.len()
99    }
100
101    /// Mark the else block of an if.
102    pub fn mark_else(&mut self) -> Result<ControlStackFrame> {
103        let last = self
104            .stack
105            .last_mut()
106            .ok_or_else(|| Error::ControlStackUnderflow)?;
107
108        if last.ty != ControlStackFrameType::If(false) {
109            return Err(Error::InvalidElseBlock(last.original_pc_offset));
110        }
111
112        last.ty = ControlStackFrameType::If(true);
113        Ok(last.clone())
114    }
115
116    /// Push a control stack frame.
117    pub fn push(&mut self, frame: ControlStackFrame) {
118        self.stack.push(frame);
119    }
120
121    /// Pop a control stack frame.
122    pub fn pop(&mut self) -> Result<ControlStackFrame> {
123        self.stack.pop().ok_or_else(|| Error::ControlStackUnderflow)
124    }
125
126    /// Get the label of the control stack frame at given depth.
127    pub fn label_from_depth(&self, mut depth: u32) -> Result<u16> {
128        for frame in self.stack.iter().rev() {
129            if frame.ty == ControlStackFrameType::Else {
130                continue;
131            }
132
133            if depth == 0 {
134                return Ok(frame.pc_offset());
135            }
136
137            depth -= 1;
138        }
139
140        Err(Error::InvalidDepth(depth as usize))
141    }
142
143    /// Get a reference to the frame at the given depth
144    pub fn frame_from_depth(&self, mut depth: u32) -> Result<&ControlStackFrame> {
145        for (i, frame) in self.stack.iter().rev().enumerate() {
146            if frame.ty == ControlStackFrameType::Else {
147                continue;
148            }
149
150            if depth == 0 {
151                return Ok(&self.stack[self.stack.len() - 1 - i]);
152            }
153
154            depth -= 1;
155        }
156
157        Err(Error::InvalidDepth(depth as usize))
158    }
159
160    /// Check if a branch at the given depth would exit the function
161    pub fn is_exit_branch(&self, depth: u32) -> bool {
162        // If depth exceeds our stack, it's definitely an exit
163        if depth as usize >= self.depth() {
164            return true;
165        }
166
167        // Get the frame that would be the target
168        if let Ok(frame) = self.frame_from_depth(depth) {
169            // If it's a Block at the outermost level (index 0), it might be a function boundary
170            if matches!(frame.ty, ControlStackFrameType::Block) {
171                for (i, f) in self.stack.iter().enumerate() {
172                    if f.original_pc_offset == frame.original_pc_offset {
173                        return i == 0; // It's an exit if it's the outermost block
174                    }
175                }
176            }
177        }
178
179        false
180    }
181
182    /// Mark frames as potentially having early returns
183    pub fn mark_frames_with_early_return(&mut self, depth: u32) {
184        let target_idx = if depth as usize >= self.depth() {
185            // If targeting beyond the stack, mark all frames
186            0 // Start from the first frame
187        } else {
188            // Find the target frame index
189            let mut target_idx = self.depth();
190            let mut current_depth = 0;
191
192            for (i, frame) in self.stack.iter().rev().enumerate() {
193                if frame.ty == ControlStackFrameType::Else {
194                    continue;
195                }
196
197                if current_depth == depth {
198                    target_idx = self.depth() - 1 - i;
199                    break;
200                }
201
202                current_depth += 1;
203            }
204
205            target_idx
206        };
207
208        // Mark frames from target to the end
209        for i in target_idx..self.depth() {
210            self.stack[i].set_might_return_early(true);
211        }
212    }
213
214    /// Get the return type of the control stack frame at given depth.
215    pub fn ret_ty(&self, depth: usize) -> Result<BlockType> {
216        if depth == 0 {
217            return Err(Error::InvalidDepth(depth));
218        }
219
220        self.stack
221            .get(self.depth() - depth)
222            .map(|f| f.result)
223            .ok_or_else(|| Error::InvalidDepth(depth))
224    }
225
226    /// Get the type of the control stack frame at given depth.
227    pub fn ty(&self, depth: usize) -> Result<ControlStackFrameType> {
228        if depth == 0 {
229            return Err(Error::InvalidDepth(depth));
230        }
231
232        self.stack
233            .get(self.depth() - depth)
234            .map(|f| f.ty)
235            .ok_or_else(|| Error::InvalidDepth(depth))
236    }
237
238    /// Get the length of the control stack
239    pub fn len(&self) -> usize {
240        self.stack.len()
241    }
242
243    /// Check if the control stack is empty
244    pub fn is_empty(&self) -> bool {
245        self.stack.is_empty()
246    }
247}