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