Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/translate/stack.rs
3068 views
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 {
95
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
109
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
123
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
131
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.
140
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
157
pub fn is_loop(&self) -> bool {
158
match *self {
159
Self::If { .. } | Self::Block { .. } => false,
160
Self::Loop { .. } => true,
161
}
162
}
163
164
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
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.
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.
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.
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]
279
pub fn reachable(&self) -> bool {
280
self.reachable
281
}
282
}
283
284
impl FuncTranslationStacks {
285
/// Construct a new, empty, `FuncTranslationStacks`
286
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
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.
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.
321
pub(crate) fn push1(&mut self, val: Value) {
322
self.stack.push(val);
323
}
324
325
/// Push two values.
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.
332
pub(crate) fn pushn(&mut self, vals: &[Value]) {
333
self.stack.extend_from_slice(vals);
334
}
335
336
/// Pop one value.
337
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.
345
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.
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.
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.
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.
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]
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.
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
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.
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.
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
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.
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.
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.
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.
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
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 {
579
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.
592
pub fn add_handler(&mut self, tag: Option<ExceptionTag>, block: Block) {
593
self.handlers.push((tag, block));
594
}
595
596
/// Take a checkpoint.
597
pub fn take_checkpoint(&self) -> HandlerStateCheckpoint {
598
HandlerStateCheckpoint(self.handlers.len())
599
}
600
601
/// Restore to a checkpoint.
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).
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?
619
pub fn is_empty(&self) -> bool {
620
self.handlers.is_empty()
621
}
622
}
623
624