zingen/visitor/
control.rs

1//! Control flow visitors
2
3use crate::{
4    control::{ControlStackFrame, ControlStackFrameType},
5    Function, Result,
6};
7use wasmparser::{BlockType, BrTable};
8
9impl Function {
10    /// The beginning of an if construct with an implicit block.
11    pub fn _if(&mut self, blockty: BlockType) -> Result<()> {
12        // Emit iszero to check the condition.
13        self.masm._iszero()?;
14
15        // push an `If` frame to the control stack
16        let frame = ControlStackFrame::new(
17            ControlStackFrameType::If(false),
18            self.masm.pc(),
19            self.masm.sp(),
20            blockty,
21        );
22        self.control.push(frame);
23
24        // mock the stack output of the counter
25        //
26        // the program counter operators should be patched afterwards.
27        self.masm.asm.increment_sp(1)?;
28        self.masm._jumpi()?;
29
30        Ok(())
31    }
32
33    /// The begeinning of a block construct. A sequence of
34    /// instructions with a label at the end.
35    pub fn _block(&mut self, blockty: BlockType) -> Result<()> {
36        let frame = ControlStackFrame::new(
37            ControlStackFrameType::Block,
38            self.masm.pc(),
39            self.masm.sp(),
40            blockty,
41        );
42        self.masm._jumpdest()?;
43        self.control.push(frame);
44
45        Ok(())
46    }
47
48    /// A block with a label which may be used to
49    /// form loops.
50    pub fn _loop(&mut self, blockty: BlockType) -> Result<()> {
51        let frame = ControlStackFrame::new(
52            ControlStackFrameType::Loop,
53            self.masm.pc(),
54            self.masm.sp(),
55            blockty,
56        );
57
58        self.masm._jumpdest()?;
59        self.control.push(frame);
60
61        Ok(())
62    }
63
64    /// Marks an else block of an if.
65    pub fn _else(&mut self) -> Result<()> {
66        let last_frame = self.control.mark_else()?;
67
68        // push an `Else` frame to the control stack.
69        let frame = ControlStackFrame::new(
70            ControlStackFrameType::Else,
71            self.masm.pc(),
72            self.masm.sp(),
73            last_frame.result(),
74        );
75        self.control.push(frame);
76        self.masm.asm.increment_sp(1)?;
77        self.masm._jump()?;
78
79        // mark else as the jump destination of the if block.
80        self.table
81            .label(last_frame.original_pc_offset, self.masm.pc());
82        self.masm._jumpdest()?;
83
84        Ok(())
85    }
86
87    /// The select instruction selects one of its first two operands based
88    /// on whether its third oprand is zero or not.
89    ///
90    /// STACK: [cond, val2, val1] -> \[val1\] if cond is non-zero, \[val2\] otherwise.
91    pub fn _select(&mut self) -> Result<()> {
92        tracing::trace!("select");
93        self.masm._iszero()?;
94        self.masm.increment_sp(1)?;
95        self.table.label(self.masm.pc(), self.masm.pc() + 2);
96        self.masm._jumpi()?;
97        self.masm._drop()?;
98        self.masm._jumpdest()?;
99
100        Ok(())
101    }
102
103    /// Branch to a given label in an enclosing construct.
104    ///
105    /// Performs an unconditional branch.
106    pub fn _br(&mut self, depth: u32) -> Result<()> {
107        // Get the target label
108        let label = self.control.label_from_depth(depth)?;
109
110        // Check if this is a branch that would exit the function
111        let _is_exit_branch = self.control.is_exit_branch(depth);
112
113        // Mark affected frames as having potential early returns
114        self.control.mark_frames_with_early_return(depth);
115
116        // Set up jump target in the jump table
117        self.table.label(self.masm.pc(), label);
118
119        // Emit unconditional jump instruction
120        self.masm._jump()?;
121
122        Ok(())
123    }
124
125    /// Performs a conditional branch if i32 is non-zero.
126    ///
127    /// Conditional branch to a given label in an enclosing construct.
128    pub fn _br_if(&mut self, depth: u32) -> Result<()> {
129        let label = self.control.label_from_depth(depth)?;
130
131        // Register the jump target in the jump table
132        self.table.label(self.masm.pc(), label);
133
134        // for a conditional branch, we need to:
135        //
136        // increment the stack pointer (for JUMPI's arguments) and
137        self.masm.increment_sp(1)?;
138
139        // emit the conditional jump instruction
140        self.masm._jumpi()?;
141
142        Ok(())
143    }
144
145    /// A jump table which jumps to a label in an enclosing construct.
146    ///
147    /// Performs an indirect branch through an operand indexing into the
148    /// label vector that is an immediate to the instruction, or to the
149    /// default target if the operand is out of bounds.
150    pub fn _br_table(&mut self, _table: BrTable<'_>) -> Result<()> {
151        todo!()
152    }
153
154    /// Handle the end of instructions for different situations.
155    ///
156    /// - End of control flow operators.
157    /// - End of function.
158    /// - End of program.
159    pub fn _end(&mut self) -> Result<()> {
160        if let Ok(frame) = self.control.pop() {
161            return self.handle_frame_popping(frame);
162        }
163
164        let results = self.ty.results();
165        if self.is_main || self.abi.is_some() {
166            tracing::trace!("end of main function");
167            self.masm.main_return(results)
168        } else {
169            tracing::trace!("end of call");
170            self.masm.call_return(results)
171        }
172    }
173
174    /// Mark as invalid for now.
175    ///
176    /// TODO: recheck this implementation, if it is okay,
177    /// provide more docs.
178    pub fn _unreachable(&mut self) -> Result<()> {
179        self.masm._invalid()?;
180        Ok(())
181    }
182
183    /// Perform nothing in EVM bytecode.
184    pub fn _nop(&mut self) -> Result<()> {
185        Ok(())
186    }
187
188    /// Handle the popping of a frame.
189    ///
190    /// TODO: validate stack IO for all frames (#59)
191    pub(crate) fn handle_frame_popping(&mut self, frame: ControlStackFrame) -> Result<()> {
192        match frame.ty {
193            ControlStackFrameType::If(true) => Ok(()),
194            ControlStackFrameType::Block => {
195                // For blocks that might have early returns, ensure proper jump destination
196                if frame.might_return_early {
197                    // Make sure the jump table has this position as a target for the block
198                    self.table.label(frame.original_pc_offset, self.masm.pc());
199                }
200                self.masm._jumpdest()
201            }
202            ControlStackFrameType::Loop => Ok(()),
203            _ => {
204                self.table.label(frame.original_pc_offset, self.masm.pc());
205
206                // TODO: Check the stack output and make decisions
207                // how to handle the results.
208
209                // Emit JUMPDEST at the end of the control flow.
210                self.masm._jumpdest()
211            }
212        }
213    }
214}