wasmer_compiler_cranelift/translator/
func_state.rs

1// This file contains code from external sources.
2// Attributions: https://github.com/wasmerio/wasmer/blob/main/docs/ATTRIBUTIONS.md
3
4//! WebAssembly module and function translation state.
5//!
6//! The `ModuleTranslationState` struct defined in this module is used to keep track of data about
7//! the whole WebAssembly module, such as the decoded type signatures.
8//!
9//! The `FuncTranslationState` struct defined in this module is used to keep track of the WebAssembly
10//! value and control stacks during the translation of a single function.
11
12use super::func_environ::{FuncEnvironment, GlobalVariable};
13use crate::heap::Heap;
14use crate::translator::code_translator::CatchClause;
15use crate::{HashMap, Occupied, Vacant};
16use cranelift_codegen::ir::{self, Block, Inst, Value};
17use cranelift_frontend::FunctionBuilder;
18use itertools::Itertools;
19use std::vec::Vec;
20use wasmer_types::{
21    CATCH_ALL_TAG_VALUE, FunctionIndex, GlobalIndex, MemoryIndex, SignatureIndex, WasmResult,
22};
23
24/// Information about the presence of an associated `else` for an `if`, or the
25/// lack thereof.
26#[derive(Debug)]
27pub enum ElseData {
28    /// The `if` does not already have an `else` block.
29    ///
30    /// This doesn't mean that it will never have an `else`, just that we
31    /// haven't seen it yet.
32    NoElse {
33        /// If we discover that we need an `else` block, this is the jump
34        /// instruction that needs to be fixed up to point to the new `else`
35        /// block rather than the destination block after the `if...end`.
36        branch_inst: Inst,
37
38        /// The placeholder block we're replacing.
39        placeholder: Block,
40    },
41
42    /// We have already allocated an `else` block.
43    ///
44    /// Usually we don't know whether we will hit an `if .. end` or an `if
45    /// .. else .. end`, but sometimes we can tell based on the block's type
46    /// signature that the signature is not valid if there isn't an `else`. In
47    /// these cases, we pre-allocate the `else` block.
48    WithElse {
49        /// This is the `else` block.
50        else_block: Block,
51    },
52}
53
54/// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following
55/// fields:
56///
57/// - `destination`: reference to the `Block` that will hold the code after the control block;
58/// - `num_return_values`: number of values returned by the control block;
59/// - `original_stack_size`: size of the value stack at the beginning of the control block.
60///
61/// Moreover, the `if` frame has the `branch_inst` field that points to the `brz` instruction
62/// separating the `true` and `false` branch. The `loop` frame has a `header` field that references
63/// the `Block` that contains the beginning of the body of the loop.
64#[derive(Debug)]
65pub enum ControlStackFrame {
66    If {
67        destination: Block,
68        else_data: ElseData,
69        num_param_values: usize,
70        num_return_values: usize,
71        original_stack_size: usize,
72        exit_is_branched_to: bool,
73        blocktype: wasmer_compiler::wasmparser::BlockType,
74        /// Was the head of the `if` reachable?
75        head_is_reachable: bool,
76        /// What was the reachability at the end of the consequent?
77        ///
78        /// This is `None` until we're finished translating the consequent, and
79        /// is set to `Some` either by hitting an `else` when we will begin
80        /// translating the alternative, or by hitting an `end` in which case
81        /// there is no alternative.
82        consequent_ends_reachable: Option<bool>,
83        // Note: no need for `alternative_ends_reachable` because that is just
84        // `state.reachable` when we hit the `end` in the `if .. else .. end`.
85    },
86    Block {
87        destination: Block,
88        num_param_values: usize,
89        num_return_values: usize,
90        original_stack_size: usize,
91        exit_is_branched_to: bool,
92        /// When this block corresponds to a try-table, keep the handler state
93        /// checkpoint and the list of catch blocks to seal once the scope ends.
94        try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,
95    },
96    Loop {
97        destination: Block,
98        header: Block,
99        num_param_values: usize,
100        num_return_values: usize,
101        original_stack_size: usize,
102    },
103}
104
105/// Helper methods for the control stack objects.
106impl ControlStackFrame {
107    pub fn num_return_values(&self) -> usize {
108        match *self {
109            Self::If {
110                num_return_values, ..
111            }
112            | Self::Block {
113                num_return_values, ..
114            }
115            | Self::Loop {
116                num_return_values, ..
117            } => num_return_values,
118        }
119    }
120    pub fn num_param_values(&self) -> usize {
121        match *self {
122            Self::If {
123                num_param_values, ..
124            }
125            | Self::Block {
126                num_param_values, ..
127            }
128            | Self::Loop {
129                num_param_values, ..
130            } => num_param_values,
131        }
132    }
133    pub fn following_code(&self) -> Block {
134        match *self {
135            Self::If { destination, .. }
136            | Self::Block { destination, .. }
137            | Self::Loop { destination, .. } => destination,
138        }
139    }
140    pub fn br_destination(&self) -> Block {
141        match *self {
142            Self::If { destination, .. } | Self::Block { destination, .. } => destination,
143            Self::Loop { header, .. } => header,
144        }
145    }
146    /// Private helper. Use `truncate_value_stack_to_else_params()` or
147    /// `truncate_value_stack_to_original_size()` to restore value-stack state.
148    fn original_stack_size(&self) -> usize {
149        match *self {
150            Self::If {
151                original_stack_size,
152                ..
153            }
154            | Self::Block {
155                original_stack_size,
156                ..
157            }
158            | Self::Loop {
159                original_stack_size,
160                ..
161            } => original_stack_size,
162        }
163    }
164    pub fn is_loop(&self) -> bool {
165        match *self {
166            Self::If { .. } | Self::Block { .. } => false,
167            Self::Loop { .. } => true,
168        }
169    }
170
171    pub fn exit_is_branched_to(&self) -> bool {
172        match *self {
173            Self::If {
174                exit_is_branched_to,
175                ..
176            }
177            | Self::Block {
178                exit_is_branched_to,
179                ..
180            } => exit_is_branched_to,
181            Self::Loop { .. } => false,
182        }
183    }
184
185    pub fn set_branched_to_exit(&mut self) {
186        match *self {
187            Self::If {
188                ref mut exit_is_branched_to,
189                ..
190            }
191            | Self::Block {
192                ref mut exit_is_branched_to,
193                ..
194            } => *exit_is_branched_to = true,
195            Self::Loop { .. } => {}
196        }
197    }
198
199    /// Pop values from the value stack so that it is left at the
200    /// input-parameters to an else-block.
201    pub fn truncate_value_stack_to_else_params(&self, stack: &mut Vec<Value>) {
202        debug_assert!(matches!(self, &Self::If { .. }));
203        stack.truncate(self.original_stack_size());
204    }
205
206    /// Pop values from the value stack so that it is left at the state it was
207    /// before this control-flow frame.
208    pub fn truncate_value_stack_to_original_size(&self, stack: &mut Vec<Value>) {
209        // The "If" frame pushes its parameters twice, so they're available to the else block
210        // (see also `FuncTranslationState::push_if`).
211        // Yet, the original_stack_size member accounts for them only once, so that the else
212        // block can see the same number of parameters as the consequent block. As a matter of
213        // fact, we need to substract an extra number of parameter values for if blocks.
214        let num_duplicated_params = match self {
215            &Self::If {
216                num_param_values, ..
217            } => {
218                debug_assert!(num_param_values <= self.original_stack_size());
219                num_param_values
220            }
221            _ => 0,
222        };
223        stack.truncate(self.original_stack_size() - num_duplicated_params);
224    }
225
226    /// Restore exception handler state and seal catch blocks when exiting a
227    /// try-table scope.
228    pub fn restore_catch_handlers(
229        &self,
230        handlers: &mut HandlerState,
231        builder: &mut FunctionBuilder,
232    ) {
233        if let Self::Block {
234            try_table_info: Some((checkpoint, catch_blocks)),
235            ..
236        } = self
237        {
238            handlers.restore_checkpoint(*checkpoint);
239            for block in catch_blocks {
240                builder.seal_block(*block);
241            }
242        }
243    }
244}
245
246/// Contains information passed along during a function's translation and that records:
247///
248/// - The current value and control stacks.
249/// - The depth of the two unreachable control blocks stacks, that are manipulated when translating
250///   unreachable code;
251pub struct FuncTranslationState {
252    /// A stack of values corresponding to the active values in the input wasm function at this
253    /// point.
254    pub(crate) stack: Vec<Value>,
255    /// A stack of active control flow operations at this point in the input wasm function.
256    pub(crate) control_stack: Vec<ControlStackFrame>,
257    /// Exception handler state used to attach catch blocks to try-calls.
258    pub(crate) handlers: HandlerState,
259    /// Is the current translation state still reachable? This is false when translating operators
260    /// like End, Return, or Unreachable.
261    pub(crate) reachable: bool,
262
263    // Map of global variables that have already been created by `FuncEnvironment::make_global`.
264    globals: HashMap<GlobalIndex, GlobalVariable>,
265
266    // Map of heaps that have been created by `FuncEnvironment::make_heap`.
267    heaps: HashMap<MemoryIndex, Heap>,
268
269    // Map of indirect call signatures that have been created by
270    // `FuncEnvironment::make_indirect_sig()`.
271    // Stores both the signature reference and the number of WebAssembly arguments
272    signatures: HashMap<SignatureIndex, (ir::SigRef, usize)>,
273
274    // Imported and local functions that have been created by
275    // `FuncEnvironment::make_direct_func()`.
276    // Stores both the function reference and the number of WebAssembly arguments
277    functions: HashMap<FunctionIndex, (ir::FuncRef, usize)>,
278}
279
280// Public methods that are exposed to non-`cranelift_wasm` API consumers.
281impl FuncTranslationState {
282    /// True if the current translation state expresses reachable code, false if it is unreachable.
283    #[inline]
284    #[allow(dead_code)]
285    pub fn reachable(&self) -> bool {
286        self.reachable
287    }
288}
289
290#[derive(Clone, Copy, Debug)]
291pub(crate) struct HandlerStateCheckpoint(usize, usize);
292
293#[derive(Default)]
294pub(crate) struct HandlerState {
295    handlers: Vec<Block>,
296    clauses: Vec<CatchClause>,
297}
298
299#[derive(Debug)]
300pub(crate) struct LandingPad {
301    pub(crate) block: Block,
302    pub(crate) clauses: Vec<CatchClause>,
303}
304
305impl HandlerState {
306    pub fn add_handler(&mut self, block: Block) {
307        self.handlers.push(block);
308    }
309
310    pub fn add_clause(&mut self, clause: CatchClause) {
311        self.clauses.push(clause);
312    }
313
314    pub fn take_checkpoint(&self) -> HandlerStateCheckpoint {
315        HandlerStateCheckpoint(self.handlers.len(), self.clauses.len())
316    }
317
318    pub fn restore_checkpoint(&mut self, checkpoint: HandlerStateCheckpoint) {
319        debug_assert!(checkpoint.0 <= self.handlers.len());
320        debug_assert!(checkpoint.1 <= self.clauses.len());
321        self.handlers.truncate(checkpoint.0);
322        self.clauses.truncate(checkpoint.1);
323    }
324
325    /// Get the latest landing pad block including all the tags covered by it.
326    pub fn landing_pad(&self) -> Option<LandingPad> {
327        self.handlers.last().copied().map(|block| LandingPad {
328            block,
329            clauses: self.unique_clauses(),
330        })
331    }
332
333    /// Returns an iterator over the catch clauses in reverse order, with duplicates removed.
334    pub fn unique_clauses(&self) -> Vec<CatchClause> {
335        self.clauses
336            .iter()
337            // Starting with the inner-most try_table catch clauses.
338            .rev()
339            .unique_by(|c| c.tag_value)
340            // We can ignore every tag followed by the CatchAll.
341            .take_while_inclusive(|c| c.tag_value != CATCH_ALL_TAG_VALUE)
342            .cloned()
343            .collect()
344    }
345
346    pub fn is_empty(&self) -> bool {
347        self.handlers.is_empty()
348    }
349
350    pub fn clear(&mut self) {
351        self.handlers.clear();
352        self.clauses.clear();
353    }
354}
355
356impl FuncTranslationState {
357    /// Construct a new, empty, `FuncTranslationState`
358    pub(crate) fn new() -> Self {
359        Self {
360            stack: Vec::new(),
361            // TODO(reftypes):
362            //metadata_stack: Vec::new(),
363            control_stack: Vec::new(),
364            handlers: HandlerState::default(),
365            reachable: true,
366            globals: HashMap::new(),
367            heaps: HashMap::new(),
368            signatures: HashMap::new(),
369            functions: HashMap::new(),
370        }
371    }
372
373    fn clear(&mut self) {
374        debug_assert!(self.stack.is_empty());
375        debug_assert!(self.control_stack.is_empty());
376        debug_assert!(self.handlers.is_empty());
377        self.reachable = true;
378        self.handlers.clear();
379        self.globals.clear();
380        self.heaps.clear();
381        self.signatures.clear();
382        self.functions.clear();
383    }
384
385    /// Initialize the state for compiling a function with the given signature.
386    ///
387    /// This resets the state to containing only a single block representing the whole function.
388    /// The exit block is the last block in the function which will contain the return instruction.
389    pub(crate) fn initialize(&mut self, sig: &ir::Signature, exit_block: Block) {
390        self.clear();
391        self.push_block(
392            exit_block,
393            0,
394            sig.returns
395                .iter()
396                .filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal)
397                .count(),
398        );
399    }
400
401    /// Push a value.
402    pub(crate) fn push1(&mut self, val: Value) {
403        self.stack.push(val);
404    }
405
406    /// Push multiple values.
407    pub(crate) fn pushn(&mut self, vals: &[Value]) {
408        self.stack.extend_from_slice(vals);
409    }
410
411    /// Pop one value.
412    pub(crate) fn pop1(&mut self) -> Value {
413        self.stack
414            .pop()
415            .expect("attempted to pop a value from an empty stack")
416    }
417
418    /// Peek at the top of the stack without popping it.
419    pub(crate) fn peek1(&self) -> Value {
420        *self
421            .stack
422            .last()
423            .expect("attempted to peek at a value on an empty stack")
424    }
425
426    /// Pop two values. Return them in the order they were pushed.
427    pub(crate) fn pop2(&mut self) -> (Value, Value) {
428        let v2 = self.pop1();
429        let v1 = self.pop1();
430        (v1, v2)
431    }
432
433    /// Pop three values. Return them in the order they were pushed.
434    pub(crate) fn pop3(&mut self) -> (Value, Value, Value) {
435        let v3 = self.pop1();
436        let v2 = self.pop1();
437        let v1 = self.pop1();
438        (v1, v2, v3)
439    }
440
441    /// Helper to ensure the stack size is at least as big as `n`; note that due to
442    /// `debug_assert` this will not execute in non-optimized builds.
443    #[inline]
444    fn ensure_length_is_at_least(&self, n: usize) {
445        debug_assert!(
446            n <= self.stack.len(),
447            "attempted to access {} values but stack only has {} values",
448            n,
449            self.stack.len()
450        );
451    }
452
453    /// Pop the top `n` values on the stack.
454    ///
455    /// The popped values are not returned. Use `peekn` to look at them before popping.
456    pub(crate) fn popn(&mut self, n: usize) {
457        self.ensure_length_is_at_least(n);
458        let new_len = self.stack.len() - n;
459        self.stack.truncate(new_len);
460    }
461
462    /// Peek at the top `n` values on the stack in the order they were pushed.
463    pub(crate) fn peekn(&self, n: usize) -> &[Value] {
464        self.ensure_length_is_at_least(n);
465        &self.stack[self.stack.len() - n..]
466    }
467
468    /// Peek at the top `n` values on the stack in the order they were pushed.
469    pub(crate) fn peekn_mut(&mut self, n: usize) -> &mut [Value] {
470        self.ensure_length_is_at_least(n);
471        let len = self.stack.len();
472        &mut self.stack[len - n..]
473    }
474
475    fn push_block_impl(
476        &mut self,
477        following_code: Block,
478        num_param_types: usize,
479        num_result_types: usize,
480        try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,
481    ) {
482        debug_assert!(num_param_types <= self.stack.len());
483        self.control_stack.push(ControlStackFrame::Block {
484            destination: following_code,
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            try_table_info,
490        });
491    }
492
493    /// Push a block on the control stack.
494    pub(crate) fn push_block(
495        &mut self,
496        following_code: Block,
497        num_param_types: usize,
498        num_result_types: usize,
499    ) {
500        self.push_block_impl(following_code, num_param_types, num_result_types, None);
501    }
502
503    /// Push a try-table block on the control stack.
504    pub(crate) fn push_try_table_block(
505        &mut self,
506        following_code: Block,
507        catch_blocks: Vec<Block>,
508        num_param_types: usize,
509        num_result_types: usize,
510        checkpoint: HandlerStateCheckpoint,
511    ) {
512        self.push_block_impl(
513            following_code,
514            num_param_types,
515            num_result_types,
516            Some((checkpoint, catch_blocks)),
517        );
518    }
519
520    /// Push a loop on the control stack.
521    pub(crate) fn push_loop(
522        &mut self,
523        header: Block,
524        following_code: Block,
525        num_param_types: usize,
526        num_result_types: usize,
527    ) {
528        debug_assert!(num_param_types <= self.stack.len());
529        self.control_stack.push(ControlStackFrame::Loop {
530            header,
531            destination: following_code,
532            original_stack_size: self.stack.len() - num_param_types,
533            num_param_values: num_param_types,
534            num_return_values: num_result_types,
535        });
536    }
537
538    /// Push an if on the control stack.
539    pub(crate) fn push_if(
540        &mut self,
541        destination: Block,
542        else_data: ElseData,
543        num_param_types: usize,
544        num_result_types: usize,
545        blocktype: wasmer_compiler::wasmparser::BlockType,
546    ) {
547        debug_assert!(num_param_types <= self.stack.len());
548
549        // Push a second copy of our `if`'s parameters on the stack. This lets
550        // us avoid saving them on the side in the `ControlStackFrame` for our
551        // `else` block (if it exists), which would require a second heap
552        // allocation. See also the comment in `translate_operator` for
553        // `Operator::Else`.
554        self.stack.reserve(num_param_types);
555        for i in (self.stack.len() - num_param_types)..self.stack.len() {
556            let val = self.stack[i];
557            self.stack.push(val);
558        }
559
560        self.control_stack.push(ControlStackFrame::If {
561            destination,
562            else_data,
563            original_stack_size: self.stack.len() - num_param_types,
564            num_param_values: num_param_types,
565            num_return_values: num_result_types,
566            exit_is_branched_to: false,
567            head_is_reachable: self.reachable,
568            consequent_ends_reachable: None,
569            blocktype,
570        });
571    }
572}
573
574/// Methods for handling entity references.
575impl FuncTranslationState {
576    /// Get the `GlobalVariable` reference that should be used to access the global variable
577    /// `index`. Create the reference if necessary.
578    /// Also return the WebAssembly type of the global.
579    pub(crate) fn get_global<FE: FuncEnvironment + ?Sized>(
580        &mut self,
581        func: &mut ir::Function,
582        index: u32,
583        environ: &mut FE,
584    ) -> WasmResult<GlobalVariable> {
585        let index = GlobalIndex::from_u32(index);
586        match self.globals.entry(index) {
587            Occupied(entry) => Ok(*entry.get()),
588            Vacant(entry) => Ok(*entry.insert(environ.make_global(func, index)?)),
589        }
590    }
591
592    /// Get the `Heap` reference that should be used to access linear memory `index`.
593    /// Create the reference if necessary.
594    pub(crate) fn get_heap<FE: FuncEnvironment + ?Sized>(
595        &mut self,
596        func: &mut ir::Function,
597        index: u32,
598        environ: &mut FE,
599    ) -> WasmResult<Heap> {
600        let index = MemoryIndex::from_u32(index);
601        match self.heaps.entry(index) {
602            Occupied(entry) => Ok(*entry.get()),
603            Vacant(entry) => Ok(*entry.insert(environ.make_heap(func, index)?)),
604        }
605    }
606
607    /// Get the `SigRef` reference that should be used to make an indirect call with signature
608    /// `index`. Also return the number of WebAssembly arguments in the signature.
609    ///
610    /// Create the signature if necessary.
611    pub(crate) fn get_indirect_sig<FE: FuncEnvironment + ?Sized>(
612        &mut self,
613        func: &mut ir::Function,
614        index: u32,
615        environ: &mut FE,
616    ) -> WasmResult<(ir::SigRef, usize)> {
617        let index = SignatureIndex::from_u32(index);
618        match self.signatures.entry(index) {
619            Occupied(entry) => Ok(*entry.get()),
620            Vacant(entry) => {
621                let sig = environ.make_indirect_sig(func, index)?;
622                Ok(*entry.insert((sig, num_wasm_parameters(environ, &func.dfg.signatures[sig]))))
623            }
624        }
625    }
626
627    /// Get the `FuncRef` reference that should be used to make a direct call to function
628    /// `index`. Also return the number of WebAssembly arguments in the signature.
629    ///
630    /// Create the function reference if necessary.
631    pub(crate) fn get_direct_func<FE: FuncEnvironment + ?Sized>(
632        &mut self,
633        func: &mut ir::Function,
634        index: u32,
635        environ: &mut FE,
636    ) -> WasmResult<(ir::FuncRef, usize)> {
637        let index = FunctionIndex::from_u32(index);
638        match self.functions.entry(index) {
639            Occupied(entry) => Ok(*entry.get()),
640            Vacant(entry) => {
641                let fref = environ.make_direct_func(func, index)?;
642                let sig = func.dfg.ext_funcs[fref].signature;
643                Ok(*entry.insert((
644                    fref,
645                    num_wasm_parameters(environ, &func.dfg.signatures[sig]),
646                )))
647            }
648        }
649    }
650}
651
652fn num_wasm_parameters<FE: FuncEnvironment + ?Sized>(
653    environ: &FE,
654    signature: &ir::Signature,
655) -> usize {
656    (0..signature.params.len())
657        .filter(|index| environ.is_wasm_parameter(signature, *index))
658        .count()
659}