Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/translate/stack.rs
1692 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
11
/// Information about the presence of an associated `else` for an `if`, or the
12
/// lack thereof.
13
#[derive(Debug)]
14
pub 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)]
51
pub 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.
93
impl 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.
241
pub 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.
256
impl 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
264
impl 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.
514
pub 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
522
impl 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)]
532
pub struct HandlerStateCheckpoint(usize);
533
534
impl 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
}
567
568