Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/pulley/src/interp/tail_loop.rs
1692 views
1
//! Support executing the interpreter loop through tail-calls rather than a
2
//! source-level `loop`.
3
//!
4
//! This is an alternative means of executing the interpreter loop of Pulley.
5
//! The other method is in `match_loop.rs` which is a `loop` over a `match`
6
//! (more-or-less). This file instead transitions between opcodes with
7
//! tail-calls.
8
//!
9
//! At this time this module is more performant but disabled by default. Rust
10
//! does not have guaranteed tail call elimination on stable at this time so
11
//! this is not a suitable means of writing an interpreter loop. That being said
12
//! this is included nonetheless for us to experiment and analyze with.
13
//!
14
//! There are two methods of using this module:
15
//!
16
//! * `RUSTFLAGS=--cfg=pulley_assume_llvm_makes_tail_calls` - this compilation
17
//! flag indicates that we should assume that LLVM will optimize to making
18
//! tail calls for things that look like tail calls. Practically this
19
//! probably only happens with `--release` and for popular native
20
//! architectures. It's up to the person compiling to manually
21
//! audit/verify/test that TCO is happening.
22
//!
23
//! * `RUSTFLAGS=--cfg=pulley_tail_calls` - this compilation flag indicates that
24
//! Rust's nightly-only support for guaranteed tail calls should be used. This
25
//! uses the `become` keyword, for example. At this time this feature of Rust
26
//! is highly experimental and may not be complete. This is only lightly
27
//! tested in CI.
28
29
use super::*;
30
use crate::ExtendedOpcode;
31
use crate::decode::ExtendedOpVisitor;
32
use crate::opcode::Opcode;
33
use crate::profile::ExecutingPcRef;
34
35
/// ABI signature of each opcode handler.
36
///
37
/// Note that this "explodes" the internals of `Interpreter` to individual
38
/// arguments to help get them all into registers.
39
type Handler = fn(&mut MachineState, UnsafeBytecodeStream, ExecutingPcRef<'_>) -> Done;
40
41
/// The extra indirection through a macro is necessary to avoid a compiler error
42
/// when compiling without `#![feature(explicit_tail_calls)]` enabled (via
43
/// `--cfg pulley_tail_calls`).
44
///
45
/// It seems rustc first parses the function, encounters `become` and emits
46
/// an error about using an unstable keyword on a stable compiler, then applies
47
/// `#[cfg(...)` after parsing to disable the function.
48
///
49
/// Macro bodies are just bags of tokens; the body is not parsed until after
50
/// they are expanded, and this macro is only expanded when `pulley_tail_calls`
51
/// is enabled.
52
#[cfg(pulley_tail_calls)]
53
macro_rules! tail_call {
54
($e:expr) => {
55
become $e
56
};
57
}
58
59
#[cfg(pulley_assume_llvm_makes_tail_calls)]
60
macro_rules! tail_call {
61
($e:expr) => {
62
return $e
63
};
64
}
65
66
impl Interpreter<'_> {
67
pub fn run(self) -> Done {
68
dispatch(self.state, self.pc, self.executing_pc)
69
}
70
}
71
72
fn debug<'a>(
73
state: &'a mut MachineState,
74
pc: UnsafeBytecodeStream,
75
executing_pc: ExecutingPcRef<'a>,
76
) -> debug::Debug<'a> {
77
debug::Debug(Interpreter {
78
state,
79
pc,
80
executing_pc,
81
})
82
}
83
84
fn dispatch(
85
state: &mut MachineState,
86
pc: UnsafeBytecodeStream,
87
executing_pc: ExecutingPcRef<'_>,
88
) -> Done {
89
// Perform a dynamic dispatch through a function pointer indexed by
90
// opcode.
91
let mut debug = debug(state, pc, executing_pc);
92
debug.before_visit();
93
let Ok(opcode) = Opcode::decode(debug.bytecode());
94
let handler = OPCODE_HANDLER_TABLE[opcode as usize];
95
tail_call!(handler(debug.0.state, debug.0.pc, debug.0.executing_pc));
96
}
97
98
/// Same as `Interpreter::run`, except for extended opcodes.
99
fn run_extended(
100
state: &mut MachineState,
101
pc: UnsafeBytecodeStream,
102
pc_ref: ExecutingPcRef<'_>,
103
) -> Done {
104
let mut i = debug(state, pc, pc_ref);
105
let Ok(opcode) = ExtendedOpcode::decode(i.bytecode());
106
let handler = EXTENDED_OPCODE_HANDLER_TABLE[opcode as usize];
107
tail_call!(handler(i.0.state, i.0.pc, i.0.executing_pc));
108
}
109
110
static OPCODE_HANDLER_TABLE: [Handler; Opcode::MAX as usize + 1] = {
111
macro_rules! define_opcode_handler_table {
112
($(
113
$( #[$attr:meta] )*
114
$snake_name:ident = $name:ident $( {
115
$(
116
$( #[$field_attr:meta] )*
117
$field:ident : $field_ty:ty
118
),*
119
} )?;
120
)*) => {
121
[
122
$($snake_name,)* // refers to functions defined down below
123
run_extended,
124
]
125
};
126
}
127
128
for_each_op!(define_opcode_handler_table)
129
};
130
131
// same as above, but without a +1 for handling of extended ops as this is the
132
// extended ops.
133
static EXTENDED_OPCODE_HANDLER_TABLE: [Handler; ExtendedOpcode::MAX as usize] = {
134
macro_rules! define_extended_opcode_handler_table {
135
($(
136
$( #[$attr:meta] )*
137
$snake_name:ident = $name:ident $( {
138
$(
139
$( #[$field_attr:meta] )*
140
$field:ident : $field_ty:ty
141
),*
142
} )?;
143
)*) => {
144
[
145
$($snake_name,)* // refers to functions defined down below
146
]
147
};
148
}
149
150
for_each_extended_op!(define_extended_opcode_handler_table)
151
};
152
153
// Define a top-level function for each opcode. Each function here is the
154
// destination of the indirect return-call-indirect of above. Each function is
155
// also specialized to a single opcode and should be thoroughly inlined to
156
// ensure that everything "boils away".
157
macro_rules! define_opcode_handler {
158
($(
159
$( #[$attr:meta] )*
160
$snake_name:ident = $name:ident $( {
161
$(
162
$( #[$field_attr:meta] )*
163
$field:ident : $field_ty:ty
164
),*
165
} )?;
166
)*) => {$(
167
fn $snake_name(
168
state: &mut MachineState,
169
pc: UnsafeBytecodeStream,
170
executing_pc: ExecutingPcRef<'_>,
171
) -> Done {
172
let mut debug = debug(state, pc, executing_pc);
173
$(
174
let Ok(($($field,)*)) = crate::decode::operands::$snake_name(debug.0.bytecode());
175
)?
176
let result = debug.$snake_name($($($field),*)?);
177
debug.after_visit();
178
match result {
179
ControlFlow::Continue(()) => {
180
tail_call!(dispatch(debug.0.state, debug.0.pc, debug.0.executing_pc))
181
}
182
ControlFlow::Break(done) => done,
183
}
184
}
185
)*};
186
}
187
188
for_each_op!(define_opcode_handler);
189
for_each_extended_op!(define_opcode_handler);
190
191