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