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}