wasmtime_internal_cranelift/translate/
stack.rs

1//! State of the Wasm stack for translation into CLIF.
2//!
3//! The `FuncTranslationStacks` struct defined in this module is used to keep
4//! track of the WebAssembly value and control stacks during the translation of
5//! a single function.
6
7use cranelift_codegen::ir::{self, Block, ExceptionTag, Inst, Value};
8use cranelift_frontend::FunctionBuilder;
9use std::vec::Vec;
10
11/// Information about the presence of an associated `else` for an `if`, or the
12/// lack thereof.
13#[derive(Debug)]
14pub enum ElseData {
15    /// The `if` does not already have an `else` block.
16    ///
17    /// This doesn't mean that it will never have an `else`, just that we
18    /// haven't seen it yet.
19    NoElse {
20        /// If we discover that we need an `else` block, this is the jump
21        /// instruction that needs to be fixed up to point to the new `else`
22        /// block rather than the destination block after the `if...end`.
23        branch_inst: Inst,
24
25        /// The placeholder block we're replacing.
26        placeholder: Block,
27    },
28
29    /// We have already allocated an `else` block.
30    ///
31    /// Usually we don't know whether we will hit an `if .. end` or an `if
32    /// .. else .. end`, but sometimes we can tell based on the block's type
33    /// signature that the signature is not valid if there isn't an `else`. In
34    /// these cases, we pre-allocate the `else` block.
35    WithElse {
36        /// This is the `else` block.
37        else_block: Block,
38    },
39}
40
41/// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following
42/// fields:
43///
44/// - `destination`: reference to the `Block` that will hold the code after the control block;
45/// - `num_return_values`: number of values returned by the control block;
46/// - `original_stack_size`: size of the value stack at the beginning of the control block.
47///
48/// The `loop` frame has a `header` field that references the `Block` that contains the beginning
49/// of the body of the loop.
50#[derive(Debug)]
51pub enum ControlStackFrame {
52    If {
53        destination: Block,
54        else_data: ElseData,
55        num_param_values: usize,
56        num_return_values: usize,
57        original_stack_size: usize,
58        exit_is_branched_to: bool,
59        blocktype: wasmparser::BlockType,
60        /// Was the head of the `if` reachable?
61        head_is_reachable: bool,
62        /// What was the reachability at the end of the consequent?
63        ///
64        /// This is `None` until we're finished translating the consequent, and
65        /// is set to `Some` either by hitting an `else` when we will begin
66        /// translating the alternative, or by hitting an `end` in which case
67        /// there is no alternative.
68        consequent_ends_reachable: Option<bool>,
69        // Note: no need for `alternative_ends_reachable` because that is just
70        // `state.reachable` when we hit the `end` in the `if .. else .. end`.
71    },
72    Block {
73        destination: Block,
74        num_param_values: usize,
75        num_return_values: usize,
76        original_stack_size: usize,
77        exit_is_branched_to: bool,
78        /// If this block is a try-table block, the handler state
79        /// checkpoint to rewind to when we leave the block, and the
80        /// list of catch blocks to seal when done.
81        try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,
82    },
83    Loop {
84        destination: Block,
85        header: Block,
86        num_param_values: usize,
87        num_return_values: usize,
88        original_stack_size: usize,
89    },
90}
91
92/// Helper methods for the control stack objects.
93impl ControlStackFrame {
94    pub fn num_return_values(&self) -> usize {
95        match *self {
96            Self::If {
97                num_return_values, ..
98            }
99            | Self::Block {
100                num_return_values, ..
101            }
102            | Self::Loop {
103                num_return_values, ..
104            } => num_return_values,
105        }
106    }
107
108    pub fn num_param_values(&self) -> usize {
109        match *self {
110            Self::If {
111                num_param_values, ..
112            }
113            | Self::Block {
114                num_param_values, ..
115            }
116            | Self::Loop {
117                num_param_values, ..
118            } => num_param_values,
119        }
120    }
121
122    pub fn following_code(&self) -> Block {
123        match *self {
124            Self::If { destination, .. }
125            | Self::Block { destination, .. }
126            | Self::Loop { destination, .. } => destination,
127        }
128    }
129
130    pub fn br_destination(&self) -> Block {
131        match *self {
132            Self::If { destination, .. } | Self::Block { destination, .. } => destination,
133            Self::Loop { header, .. } => header,
134        }
135    }
136
137    /// Private helper. Use `truncate_value_stack_to_else_params()` or
138    /// `truncate_value_stack_to_original_size()` to restore value-stack state.
139    fn original_stack_size(&self) -> usize {
140        match *self {
141            Self::If {
142                original_stack_size,
143                ..
144            }
145            | Self::Block {
146                original_stack_size,
147                ..
148            }
149            | Self::Loop {
150                original_stack_size,
151                ..
152            } => original_stack_size,
153        }
154    }
155
156    pub fn is_loop(&self) -> bool {
157        match *self {
158            Self::If { .. } | Self::Block { .. } => false,
159            Self::Loop { .. } => true,
160        }
161    }
162
163    pub fn exit_is_branched_to(&self) -> bool {
164        match *self {
165            Self::If {
166                exit_is_branched_to,
167                ..
168            }
169            | Self::Block {
170                exit_is_branched_to,
171                ..
172            } => exit_is_branched_to,
173            Self::Loop { .. } => false,
174        }
175    }
176
177    pub fn set_branched_to_exit(&mut self) {
178        match *self {
179            Self::If {
180                ref mut exit_is_branched_to,
181                ..
182            }
183            | Self::Block {
184                ref mut exit_is_branched_to,
185                ..
186            } => *exit_is_branched_to = true,
187            Self::Loop { .. } => {}
188        }
189    }
190
191    /// Pop values from the value stack so that it is left at the
192    /// input-parameters to an else-block.
193    pub fn truncate_value_stack_to_else_params(&self, stack: &mut Vec<Value>) {
194        debug_assert!(matches!(self, &ControlStackFrame::If { .. }));
195        stack.truncate(self.original_stack_size());
196    }
197
198    /// Pop values from the value stack so that it is left at the state it was
199    /// before this control-flow frame.
200    pub fn truncate_value_stack_to_original_size(&self, stack: &mut Vec<Value>) {
201        // The "If" frame pushes its parameters twice, so they're available to the else block
202        // (see also `FuncTranslationStacks::push_if`).
203        // Yet, the original_stack_size member accounts for them only once, so that the else
204        // block can see the same number of parameters as the consequent block. As a matter of
205        // fact, we need to subtract an extra number of parameter values for if blocks.
206        let num_duplicated_params = match self {
207            &ControlStackFrame::If {
208                num_param_values, ..
209            } => {
210                debug_assert!(num_param_values <= self.original_stack_size());
211                num_param_values
212            }
213            _ => 0,
214        };
215        stack.truncate(self.original_stack_size() - num_duplicated_params);
216    }
217
218    /// Restore the catch-handlers as they were outside of this block.
219    pub fn restore_catch_handlers(
220        &self,
221        handlers: &mut HandlerState,
222        builder: &mut FunctionBuilder,
223    ) {
224        match self {
225            ControlStackFrame::Block {
226                try_table_info: Some((ckpt, catch_blocks)),
227                ..
228            } => {
229                handlers.restore_checkpoint(*ckpt);
230                for block in catch_blocks {
231                    builder.seal_block(*block);
232                }
233            }
234            _ => {}
235        }
236    }
237}
238
239/// Keeps track of Wasm's operand and control stacks, as well as reachability
240/// for each control frame.
241pub struct FuncTranslationStacks {
242    /// A stack of values corresponding to the active values in the input wasm function at this
243    /// point.
244    pub(crate) stack: Vec<Value>,
245    /// A stack of active control flow operations at this point in the input wasm function.
246    pub(crate) control_stack: Vec<ControlStackFrame>,
247    /// Exception handler state, updated as we enter and exit
248    /// `try_table` scopes and attached to each call that we make.
249    pub(crate) handlers: HandlerState,
250    /// Is the current translation state still reachable? This is false when translating operators
251    /// like End, Return, or Unreachable.
252    pub(crate) reachable: bool,
253}
254
255// Public methods that are exposed to non- API consumers.
256impl FuncTranslationStacks {
257    /// True if the current translation state expresses reachable code, false if it is unreachable.
258    #[inline]
259    pub fn reachable(&self) -> bool {
260        self.reachable
261    }
262}
263
264impl FuncTranslationStacks {
265    /// Construct a new, empty, `FuncTranslationStacks`
266    pub(crate) fn new() -> Self {
267        Self {
268            stack: Vec::new(),
269            control_stack: Vec::new(),
270            handlers: HandlerState::default(),
271            reachable: true,
272        }
273    }
274
275    fn clear(&mut self) {
276        debug_assert!(self.stack.is_empty());
277        debug_assert!(self.control_stack.is_empty());
278        debug_assert!(self.handlers.is_empty());
279        self.reachable = true;
280    }
281
282    /// Initialize the state for compiling a function with the given signature.
283    ///
284    /// This resets the state to containing only a single block representing the whole function.
285    /// The exit block is the last block in the function which will contain the return instruction.
286    pub(crate) fn initialize(&mut self, sig: &ir::Signature, exit_block: Block) {
287        self.clear();
288        self.push_block(
289            exit_block,
290            0,
291            sig.returns
292                .iter()
293                .filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal)
294                .count(),
295        );
296    }
297
298    /// Push a value.
299    pub(crate) fn push1(&mut self, val: Value) {
300        self.stack.push(val);
301    }
302
303    /// Push two values.
304    pub(crate) fn push2(&mut self, val1: Value, val2: Value) {
305        self.stack.push(val1);
306        self.stack.push(val2);
307    }
308
309    /// Push multiple values.
310    pub(crate) fn pushn(&mut self, vals: &[Value]) {
311        self.stack.extend_from_slice(vals);
312    }
313
314    /// Pop one value.
315    pub(crate) fn pop1(&mut self) -> Value {
316        self.stack
317            .pop()
318            .expect("attempted to pop a value from an empty stack")
319    }
320
321    /// Peek at the top of the stack without popping it.
322    pub(crate) fn peek1(&self) -> Value {
323        *self
324            .stack
325            .last()
326            .expect("attempted to peek at a value on an empty stack")
327    }
328
329    /// Pop two values. Return them in the order they were pushed.
330    pub(crate) fn pop2(&mut self) -> (Value, Value) {
331        let v2 = self.stack.pop().unwrap();
332        let v1 = self.stack.pop().unwrap();
333        (v1, v2)
334    }
335
336    /// Pop three values. Return them in the order they were pushed.
337    pub(crate) fn pop3(&mut self) -> (Value, Value, Value) {
338        let v3 = self.stack.pop().unwrap();
339        let v2 = self.stack.pop().unwrap();
340        let v1 = self.stack.pop().unwrap();
341        (v1, v2, v3)
342    }
343
344    /// Pop four values. Return them in the order they were pushed.
345    pub(crate) fn pop4(&mut self) -> (Value, Value, Value, Value) {
346        let v4 = self.stack.pop().unwrap();
347        let v3 = self.stack.pop().unwrap();
348        let v2 = self.stack.pop().unwrap();
349        let v1 = self.stack.pop().unwrap();
350        (v1, v2, v3, v4)
351    }
352
353    /// Pop five values. Return them in the order they were pushed.
354    pub(crate) fn pop5(&mut self) -> (Value, Value, Value, Value, Value) {
355        let v5 = self.stack.pop().unwrap();
356        let v4 = self.stack.pop().unwrap();
357        let v3 = self.stack.pop().unwrap();
358        let v2 = self.stack.pop().unwrap();
359        let v1 = self.stack.pop().unwrap();
360        (v1, v2, v3, v4, v5)
361    }
362
363    /// Helper to ensure the stack size is at least as big as `n`; note that due to
364    /// `debug_assert` this will not execute in non-optimized builds.
365    #[inline]
366    fn ensure_length_is_at_least(&self, n: usize) {
367        debug_assert!(
368            n <= self.stack.len(),
369            "attempted to access {} values but stack only has {} values",
370            n,
371            self.stack.len()
372        )
373    }
374
375    /// Pop the top `n` values on the stack.
376    ///
377    /// The popped values are not returned. Use `peekn` to look at them before popping.
378    pub(crate) fn popn(&mut self, n: usize) {
379        self.ensure_length_is_at_least(n);
380        let new_len = self.stack.len() - n;
381        self.stack.truncate(new_len);
382    }
383
384    /// Peek at the top `n` values on the stack in the order they were pushed.
385    pub(crate) fn peekn(&self, n: usize) -> &[Value] {
386        self.ensure_length_is_at_least(n);
387        &self.stack[self.stack.len() - n..]
388    }
389
390    /// Peek at the top `n` values on the stack in the order they were pushed.
391    pub(crate) fn peekn_mut(&mut self, n: usize) -> &mut [Value] {
392        self.ensure_length_is_at_least(n);
393        let len = self.stack.len();
394        &mut self.stack[len - n..]
395    }
396
397    fn push_block_impl(
398        &mut self,
399        following_code: Block,
400        num_param_types: usize,
401        num_result_types: usize,
402        try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,
403    ) {
404        debug_assert!(num_param_types <= self.stack.len());
405        self.control_stack.push(ControlStackFrame::Block {
406            destination: following_code,
407            original_stack_size: self.stack.len() - num_param_types,
408            num_param_values: num_param_types,
409            num_return_values: num_result_types,
410            exit_is_branched_to: false,
411            try_table_info,
412        });
413    }
414
415    /// Push a block on the control stack.
416    pub(crate) fn push_block(
417        &mut self,
418        following_code: Block,
419        num_param_types: usize,
420        num_result_types: usize,
421    ) {
422        self.push_block_impl(following_code, num_param_types, num_result_types, None);
423    }
424
425    /// Push a try-table block on the control stack.
426    pub(crate) fn push_try_table_block(
427        &mut self,
428        following_code: Block,
429        catch_blocks: Vec<Block>,
430        num_param_types: usize,
431        num_result_types: usize,
432        checkpoint: HandlerStateCheckpoint,
433    ) {
434        self.push_block_impl(
435            following_code,
436            num_param_types,
437            num_result_types,
438            Some((checkpoint, catch_blocks)),
439        );
440    }
441
442    /// Push a loop on the control stack.
443    pub(crate) fn push_loop(
444        &mut self,
445        header: Block,
446        following_code: Block,
447        num_param_types: usize,
448        num_result_types: usize,
449    ) {
450        debug_assert!(num_param_types <= self.stack.len());
451        self.control_stack.push(ControlStackFrame::Loop {
452            header,
453            destination: following_code,
454            original_stack_size: self.stack.len() - num_param_types,
455            num_param_values: num_param_types,
456            num_return_values: num_result_types,
457        });
458    }
459
460    /// Push an if on the control stack.
461    pub(crate) fn push_if(
462        &mut self,
463        destination: Block,
464        else_data: ElseData,
465        num_param_types: usize,
466        num_result_types: usize,
467        blocktype: wasmparser::BlockType,
468    ) {
469        debug_assert!(num_param_types <= self.stack.len());
470
471        // Push a second copy of our `if`'s parameters on the stack. This lets
472        // us avoid saving them on the side in the `ControlStackFrame` for our
473        // `else` block (if it exists), which would require a second heap
474        // allocation. See also the comment in `translate_operator` for
475        // `Operator::Else`.
476        self.stack.reserve(num_param_types);
477        for i in (self.stack.len() - num_param_types)..self.stack.len() {
478            let val = self.stack[i];
479            self.stack.push(val);
480        }
481
482        self.control_stack.push(ControlStackFrame::If {
483            destination,
484            else_data,
485            original_stack_size: self.stack.len() - num_param_types,
486            num_param_values: num_param_types,
487            num_return_values: num_result_types,
488            exit_is_branched_to: false,
489            head_is_reachable: self.reachable,
490            consequent_ends_reachable: None,
491            blocktype,
492        });
493    }
494}
495
496/// Exception handler state.
497///
498/// We update this state as we enter and exit `try_table` scopes. When
499/// we visit a call, we use this state to attach handler info to a
500/// `try_call` CLIF instruction.
501///
502/// Note that although handlers are lexically-scoped, and we could
503/// optimize away shadowing, this is fairly subtle, because handler
504/// order also matters (two *distinct* tag indices in our module are
505/// not necessarily distinct: tag imports can create aliasing). Rather
506/// than attempt to keep an ordered map and also remove shadowing, we
507/// follow the Wasm spec more closely: handlers are on "the stack" and
508/// inner handlers win over outer handlers. Within a single
509/// `try_table`, we push handlers *in reverse*, because the semantics
510/// of handler matching in `try_table` are left-to-right; this allows
511/// us to *flatten* the LIFO stack of `try_table`s with left-to-right
512/// scans within a table into a single stack we scan backward from the
513/// end.
514pub struct HandlerState {
515    /// List of pairs mapping from CLIF-level exception tag to
516    /// CLIF-level block. We will have already filled in these blocks
517    /// with the appropriate branch implementation when we start the
518    /// `try_table` scope.
519    pub(crate) handlers: Vec<(Option<ExceptionTag>, Block)>,
520}
521
522impl core::default::Default for HandlerState {
523    fn default() -> Self {
524        HandlerState { handlers: vec![] }
525    }
526}
527
528/// A checkpoint in the handler state. Can be restored in LIFO order
529/// only: the last-taken checkpoint can be restored first, then the
530/// one before it, etc.
531#[derive(Clone, Copy, Debug)]
532pub struct HandlerStateCheckpoint(usize);
533
534impl HandlerState {
535    /// Set a given tag's handler to a given CLIF block.
536    pub fn add_handler(&mut self, tag: Option<ExceptionTag>, block: Block) {
537        self.handlers.push((tag, block));
538    }
539
540    /// Take a checkpoint.
541    pub fn take_checkpoint(&self) -> HandlerStateCheckpoint {
542        HandlerStateCheckpoint(self.handlers.len())
543    }
544
545    /// Restore to a checkpoint.
546    pub fn restore_checkpoint(&mut self, ckpt: HandlerStateCheckpoint) {
547        assert!(ckpt.0 <= self.handlers.len());
548        self.handlers.truncate(ckpt.0);
549    }
550
551    /// Get an iterator over handlers. The exception-matching
552    /// semantics are to take the *first* match in this sequence; that
553    /// is, this returns the sequence of handlers latest-first (top of
554    /// stack first).
555    pub fn handlers(&self) -> impl Iterator<Item = (Option<ExceptionTag>, Block)> + '_ {
556        self.handlers
557            .iter()
558            .map(|(tag, block)| (*tag, *block))
559            .rev()
560    }
561
562    /// Are there no handlers registered?
563    pub fn is_empty(&self) -> bool {
564        self.handlers.is_empty()
565    }
566}