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 at this time so this is not
11 //! a suitable means of writing an interpreter loop. That being said this is
12 //! 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 not even complete. It only passes `cargo check`
27 //!   at this time but doesn't actually run anywhere.
28 
29 use super::*;
30 use crate::decode::{unwrap_uninhabited, ExtendedOpVisitor};
31 use crate::opcode::Opcode;
32 use crate::ExtendedOpcode;
33 
34 type Handler = fn(Interpreter<'_>) -> Done;
35 
36 /// The extra indirection through a macro is necessary to avoid a compiler error
37 /// when compiling without `#![feature(explicit_tail_calls)]` enabled (via
38 /// `--cfg pulley_tail_calls`).
39 ///
40 /// It seems rustc first parses the function, encounters `become` and emits
41 /// an error about using an unstable keyword on a stable compiler, then applies
42 /// `#[cfg(...)` after parsing to disable the function.
43 ///
44 /// Macro bodies are just bags of tokens; the body is not parsed until after
45 /// they are expanded, and this macro is only expanded when `pulley_tail_calls`
46 /// is enabled.
47 #[cfg(pulley_tail_calls)]
48 macro_rules! tail_call {
49     ($e:expr) => {
50         become $e
51     };
52 }
53 
54 #[cfg(pulley_assume_llvm_makes_tail_calls)]
55 macro_rules! tail_call {
56     ($e:expr) => {
57         return $e
58     };
59 }
60 
61 impl Interpreter<'_> {
62     pub fn run(mut self) -> Done {
63         // Perform a dynamic dispatch through a function pointer indexed by
64         // opcode.
65         let opcode = unwrap_uninhabited(Opcode::decode(self.bytecode()));
66         let handler = OPCODE_HANDLER_TABLE[opcode as usize];
67         tail_call!(handler(self));
68     }
69 }
70 
71 /// Same as `Interpreter::run`, except for extended opcodes.
72 fn run_extended(mut i: Interpreter<'_>) -> Done {
73     let opcode = unwrap_uninhabited(ExtendedOpcode::decode(i.bytecode()));
74     let handler = EXTENDED_OPCODE_HANDLER_TABLE[opcode as usize];
75     tail_call!(handler(i));
76 }
77 
78 static OPCODE_HANDLER_TABLE: [Handler; Opcode::MAX as usize + 1] = {
79     macro_rules! define_opcode_handler_table {
80         ($(
81             $( #[$attr:meta] )*
82             $snake_name:ident = $name:ident $( {
83                 $(
84                     $( #[$field_attr:meta] )*
85                     $field:ident : $field_ty:ty
86                 ),*
87             } )?;
88         )*) => {
89             [
90                 $($snake_name,)* // refers to functions defined down below
91                 run_extended,
92             ]
93         };
94     }
95 
96     for_each_op!(define_opcode_handler_table)
97 };
98 
99 // same as above, but without a +1 for handling of extended ops as this is the
100 // extended ops.
101 static EXTENDED_OPCODE_HANDLER_TABLE: [Handler; ExtendedOpcode::MAX as usize] = {
102     macro_rules! define_extended_opcode_handler_table {
103         ($(
104             $( #[$attr:meta] )*
105             $snake_name:ident = $name:ident $( {
106                 $(
107                     $( #[$field_attr:meta] )*
108                     $field:ident : $field_ty:ty
109                 ),*
110             } )?;
111         )*) => {
112             [
113                 $($snake_name,)* // refers to functions defined down below
114             ]
115         };
116     }
117 
118     for_each_extended_op!(define_extended_opcode_handler_table)
119 };
120 
121 // Define a top-level function for each opcode. Each function here is the
122 // destination of the indirect return-call-indirect of above. Each function is
123 // also specialized to a single opcode and should be thoroughly inlined to
124 // ensure that everything "boils away".
125 macro_rules! define_opcode_handler {
126     ($(
127         $( #[$attr:meta] )*
128         $snake_name:ident = $name:ident $( {
129             $(
130                 $( #[$field_attr:meta] )*
131                 $field:ident : $field_ty:ty
132             ),*
133         } )?;
134     )*) => {$(
135         fn $snake_name(mut i: Interpreter<'_>) -> Done {
136             $(
137                 let ($($field,)*) = unwrap_uninhabited(
138                     crate::decode::operands::$snake_name(i.bytecode())
139                 );
140             )?
141             let _ = &mut i;
142             let mut debug = debug::Debug(i);
143             match debug.$snake_name($($($field),*)?) {
144                 ControlFlow::Continue(()) => tail_call!(debug.0.run()),
145                 ControlFlow::Break(done) => done,
146             }
147         }
148     )*};
149 }
150 
151 for_each_op!(define_opcode_handler);
152 for_each_extended_op!(define_opcode_handler);
153