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 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