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<'_> {
run(self) -> Done67 pub fn run(self) -> Done {
68 dispatch(self.state, self.pc, self.executing_pc)
69 }
70 }
71
debug<'a>( state: &'a mut MachineState, pc: UnsafeBytecodeStream, executing_pc: ExecutingPcRef<'a>, ) -> debug::Debug<'a>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
dispatch( state: &mut MachineState, pc: UnsafeBytecodeStream, executing_pc: ExecutingPcRef<'_>, ) -> Done84 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.
run_extended( state: &mut MachineState, pc: UnsafeBytecodeStream, pc_ref: ExecutingPcRef<'_>, ) -> Done99 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 static EXTENDED_OPCODE_HANDLER_TABLE: [Handler; ExtendedOpcode::MAX as usize + 1] = {
132 macro_rules! define_extended_opcode_handler_table {
133 ($(
134 $( #[$attr:meta] )*
135 $snake_name:ident = $name:ident $( {
136 $(
137 $( #[$field_attr:meta] )*
138 $field:ident : $field_ty:ty
139 ),*
140 } )?;
141 )*) => {
142 [
143 $($snake_name,)* // refers to functions defined down below
144 ]
145 };
146 }
147
148 for_each_extended_op!(define_extended_opcode_handler_table)
149 };
150
151 // Define a top-level function for each opcode. Each function here is the
152 // destination of the indirect return-call-indirect of above. Each function is
153 // also specialized to a single opcode and should be thoroughly inlined to
154 // ensure that everything "boils away".
155 macro_rules! define_opcode_handler {
156 ($(
157 $( #[$attr:meta] )*
158 $snake_name:ident = $name:ident $( {
159 $(
160 $( #[$field_attr:meta] )*
161 $field:ident : $field_ty:ty
162 ),*
163 } )?;
164 )*) => {$(
165 fn $snake_name(
166 state: &mut MachineState,
167 pc: UnsafeBytecodeStream,
168 executing_pc: ExecutingPcRef<'_>,
169 ) -> Done {
170 let mut debug = debug(state, pc, executing_pc);
171 $(
172 let Ok(($($field,)*)) = crate::decode::operands::$snake_name(debug.0.bytecode());
173 )?
174 let result = debug.$snake_name($($($field),*)?);
175 debug.after_visit();
176 match result {
177 ControlFlow::Continue(()) => {
178 tail_call!(dispatch(debug.0.state, debug.0.pc, debug.0.executing_pc))
179 }
180 ControlFlow::Break(done) => done,
181 }
182 }
183 )*};
184 }
185
186 for_each_op!(define_opcode_handler);
187 for_each_extended_op!(define_opcode_handler);
188