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